为 https 正确设置“默认”nginx 服务器

92 ssl nginx https

我有几台服务器在同一台机器上运行,有些只使用 http,有些同时使用 http 和 https。在单独的文件中定义了几个服务器块,这些文件包含在主配置文件中。

我已经为 http 设置了一个“默认”服务器,它将为与其他配置文件中的任何其他 server_names 不匹配的请求提供通用的“维护页面”。http 默认服务器按预期工作,它使用 server_name "_" 并且它首先出现在包含列表中(因为我观察到在跨服务器重复 server_names 的情况下,使用第一个出现的)。这很好用。

我希望完全相同的服务器块(仅将“listen 80 default_server”切换为“listen 443 default_server”,而不是服务页面“return 444”),但事实并非如此。相反,看起来新的默认 https 服务器实际上是在获取所有传入的 https 连接并导致它们失败,尽管其他服务器块对传入请求有更合适的 server_names。删除新的默认 https 服务器将导致恢复半正确行为:带有 https 的网站将全部正确加载;但是没有 https 的网站都将被路由到包含文件中的第一个 https 服务器(根据文档,如果没有出现“default_server”,那么出现的第一个服务器块将是“default”)。

所以我的问题是,在 nginx 中为 ssl 连接定义“默认服务器”的正确方法是什么?为什么当我显式设置“default_server”时,它会变得贪婪并获取所有连接,而当我隐式让 nginx 决定“默认服务器”时,它会像我期望的那样工作(将不正确的服务器设置为默认值,而其他真实服务器)行为正确)?

这是我的“默认服务器”。Http 可以在不破坏其他服务器的情况下工作。Https 破坏其他服务器并消耗所有服务器。

server {
    listen 443 ssl default_server;
    server_name _;

    access_log /var/log/nginx/maintenance.access.log;
    error_log /var/log/nginx/maintenance.error.log error;

    return 444;
}

server {
    listen *:80 default_server;
    server_name _;
    charset utf-8;

    access_log /var/log/nginx/maintenance.access.log;
    error_log /var/log/nginx/maintenance.error.log error;

    root /home/path/to/templates;

    location / {
        return 503;
    }

    error_page 503 @maintenance;

    location @maintenance {
        rewrite ^(.*)$ /maintenance.html break;
    }
}
Run Code Online (Sandbox Code Playgroud)

你们有人看到这里可能有什么问题吗?

Ifn*_*not 34

我设法使用 nginx 在单个 IP 上配置了共享专用主机。默认 HTTP 和 HTTPS 为传入的未知域提供 404。

1 - 创建默认区域

由于 nginx 正在以 ascii 顺序加载虚拟主机,因此您应该00-default/etc/nginx/sites-enabled.

2 - 填充默认区域

00-default用默认的虚拟主机填充你的。这是我正在使用的区域:

server {
    server_name _;
    listen       80  default_server;
    return       404;
}


server {
    listen 443 ssl;
    server_name _;
    ssl_certificate /etc/nginx/ssl/nginx.crt;
    ssl_certificate_key /etc/nginx/ssl/nginx.key;
    return       404;
}
Run Code Online (Sandbox Code Playgroud)

3 - 创建自签名证书、测试和重新加载

您将需要创建一个自签名证书到/etc/nginx/ssl/nginx.crt.

创建一个默认的自签名证书:

sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /etc/nginx/ssl/nginx.key -out /etc/nginx/ssl/nginx.crt
Run Code Online (Sandbox Code Playgroud)

只是提醒:

  • 在重新加载/重新启动之前测试 nginx 配置: nginx -t
  • 重新加载享受: sudo service nginx reload

希望能帮助到你。

  • 无法解析,因为包罗万象必须匹配任何域。没有 SSL 可以通配所有域。想象一下,如果您在服务器上欺骗 google.fr 地址,您将能够将您的服务器验证为 google.fr。这将是一个严重的安全问题:( (6认同)
  • `openssl` 命令首先需要 `mkdir /etc/nginx/ssl` (4认同)
  • 不能解决浏览器访问不安全站点的警告。 (3认同)
  • 是的,我想这是有道理的。不幸的是,在 Chrome 中,您在看到 404 页面之前会收到一个可怕的警告,这比让服务器简单地拒绝流量更糟糕。使它看起来像服务器配置错误。 (2认同)

小智 31

您没有在“默认”https 块中定义任何 ssl_certificate 或 ssl_certificate_key。尽管您没有或不想要此默认场景的真正密钥,但您仍然需要配置一个,否则 nginx 将出现您描述的不良行为。

创建一个通用名称为 * 的自签名证书并将其插入您的配置中,它将开始按您的意愿工作。

此设置下的“默认”行为是浏览器将收到证书不可信的警告,如果用户将证书添加为例外,则 nginx 将断开连接,他们将看到浏览器的默认值“无法连接”错误消息。


小智 18

我们基本上希望不惜一切代价避免配置文件中的第一个服务器定义被用作 SSL 连接的全能服务器。我们都知道它可以做到这一点(与 http 相对并使用运行良好的 default_server 配置)。

这对于 SSL(还)无法以声明方式实现,因此我们必须使用 IF 对其进行编码...

该变量$host是请求行或 http 标头中的主机名。变量$server_name是我们现在所在的服务器块的名称。

因此,如果这两者不相等,那么您为另一台主机提供了此 SSL 服务器块,因此应该被阻止。

该代码不包含对您的服务器 IP 地址的特定引用,因此它可以轻松地重用于其他服务器配置而无需修改。

例子:

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;

    ###
    ### Section: SSL

    #
    ## Check if this certificate is really served for this server_name
    ##   http://serverfault.com/questions/578648/properly-setting-up-a-default-nginx-server-for-https
    if ($host != $server_name) {
        #return 404 "this is an invalid request";
        return       444;
    }

    ...
Run Code Online (Sandbox Code Playgroud)


小智 12

要详细说明 Radmilla Mustafa 的回答:

Nginx 使用 'Host' 标头进行 server_name 匹配。它不使用 TLS SNI。这意味着对于 SSL 服务器,nginx 必须能够接受 SSL 连接,这归结为拥有证书/密钥。证书/密钥可以是任意的,例如自签名。

查看文档

因此,解决方案是:

server {
    server_name _;
    listen 80 default_server;
    listen 443 ssl default_server;

    ## To also support IPv6, uncomment this block
    # listen [::]:80 default_server;
    # listen [::]:443 ssl default_server;

    ssl_certificate <path to cert>;
    ssl_certificate_key <path to key>;
    return 404; # or whatever
}
Run Code Online (Sandbox Code Playgroud)


小智 5

To anyone who lost as much hair over this as me (spent nearly whole day on this today). I have tried almost everything, and the thing that made it finally working correctly was this stupid line:

ssl_session_tickets off;
Run Code Online (Sandbox Code Playgroud)

Building upon Ifnot's answer, my working example is:

server {
    listen 443 ssl http2 default_server;
    listen [::]:443 ssl http2 default_server;
    server_name _;

    ssl_certificate /etc/nginx/ssl/nginx.crt;
    ssl_certificate_key /etc/nginx/ssl/nginx.key;
    ssl_session_tickets off;

    return 404;
}
Run Code Online (Sandbox Code Playgroud)

I have no idea why this was necessary, the only principle I derived out of this was that nginx behaves very strangely when we don't give him what he wants.


hyp*_*not 5

如果您只想确保不返回任何数据,您可以使用以下不带证书的代码片段:

map "" $empty {
        default "";
}

server {
        listen 80 default_server;
        listen 443 ssl http2 default_server;
        listen [::]:80 default_server;
        listen [::]:443 ssl http2 default_server;

        server_name _;

        ssl_ciphers aNULL;
        ssl_certificate data:$empty;
        ssl_certificate_key data:$empty;

        return 444;
}
Run Code Online (Sandbox Code Playgroud)

  • 显然,`map ...` 东西可以用 `ssl_certificate` 指令之前的简单 `set $empty ""` 替换。它适用于我的 Nginx 1.18.0。 (2认同)

Mic*_*ton 2

如果您想绝对确定,请为不应在 HTTPS 上应答的主机和应应答的主机使用单独的 IP 地址。这也解决了“无效证书”浏览器警告问题。