具有多个域/子域和 ssl_certificate 变量的 Nginx

具有多个域/子域和 ssl_certificate 变量的 Nginx

我们有一个 Nginx,它有数百个代理,可以代理多个域/子域。多年来,它一直保持这种状态,每个新域,我们只需去那里创建一个新的 conf 文件,其中包含包含 HTTP 和 HTTPS 的服务器块。

注意到很多服务器块具有相同的代理参数,仅更改了后端,就像“嗯,我可以尝试将所有这些放在一对具有 http 和 https 的服务器块中吗?”并决定尝试一下 :)

我的想法是将带有地址的变量映射到后端和所使用的证书,因为这些是我们拥有的大多数服务器块上唯一发生变化的信息,这样对于新的域名只需在地图上添加线条即可。

HTTP 工作正常,没有任何问题,问题在于 HTTPS。

我有多个域,例如“example02.com、example01.com,...”,并对每个域使用通配符证书,因此根据主机,我将证书名称映射到变量,并在 ssl_certificate 和 ssl_certificate_key 上使用它,如下所示,我发现这只有在 Nginx 1.15 之后才有可能。(我现在使用的是 1.18)

问题是,按照我构建以下服务器块的方式,只有www.example01.com将使用正确的证书,当您尝试访问时www.example02.com,它仍将尝试加载 *.example01.com 证书。

可以为每个域创建一个服务器块,以使用不同的证书为多个子域提供服务,但现在我只是试图弄清楚是否可以只使用一对服务器块来完成此操作并检查我是否做错了什么。

map $host $backendaddr {
        default         "127.0.0.1:4444";
        www.example01.com   "127.0.0.1:4445"
        www.example02.com   "127.0.0.1:4446"
}

map $host $certificate {
        default         "example.com";
        www.example01.com   example01.com;
        www.example02.com   example02.com;
}

server {
   listen 80;
   server_name www.example01.com www.example02.com;
   access_log /var/log/nginx/access-http.log main;

   proxy_set_header Host $host;
   proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
   proxy_connect_timeout 1800;
   proxy_read_timeout 1800;
   proxy_send_timeout 1800;
   location / {
      proxy_pass http://$backendaddr$request_uri;
   }
}

server {
   listen 443 ssl;
   server_name  www.example01.com www.example02.com;
   access_log /var/log/nginx/access-https.log main;
   
   ssl_certificate ssl/$certificate.pem;
   ssl_certificate_key ssl/$certificate.key;

   proxy_set_header Host $host;
   proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
   proxy_connect_timeout 1800;
   proxy_read_timeout 1800;
   proxy_send_timeout 1800;
   location / {
      proxy_pass https://$backendaddr$request_uri;
   }
}

答案1

问题在于证书$host中使用了该变量map。TLS 连接中的请求流程如下所示:

  1. TCP 握手后,客户端向服务器发送 TLS Hello 数据包。Hello 数据包包含SNI字段,告知客户端尝试连接的虚拟主机。
  2. 服务器需要选择一个证书发送给客户端。$host变量仅从以下内容中填充:
  • 请求行中的主机名
  • Host请求标头中的主机名
  • server_name匹配请求

这里唯一可用的信息是server_name,因此www.example01.com被用作变量的值$host

为了达到您的目标,您可以尝试以下操作:

server {
    listen 443 ssl;
    server_name www.example01.com www.example02.com;
    ssl_preread on;
    ...
}

map $ssl_preread_server_name $certificate {
    default           example.com;
    www.example01.com example01.com;
    www.example02.com example02.com;
}

ssl_preread on使 nginx 根据 TLS ClientHello 数据包分配变量。$ssl_preread_server_name包含来自 ClientHello 数据包的 SNI 字段的值,然后可以使用它来选择证书。

相关内容