配置 HTTPS 服务器

要实现 nginx 配置 HTTPS 服务器,必须在 server 配置块中的监听套接字(sockets)上启用 ssl 参数,并应指定服务器证书(server certificate)和私钥(private key)文件的位置:

server {
    listen                         443 ssl;
    server_name             www.example.com;
    ssl_certificate            www.example.com.crt;
    ssl_certificate_key     www.example.com.key;
    ssl_protocols            TLSv1 TLSv1.1 TLSv1.2;
    ssl_ciphers                HIGH:!aNULL:!MD5;
    ...
}

服务器证书(server certificate)是公共实体,它被发送给连接到服务器的每个客户端。私钥(private key)是一个安全实体,应该存储在一个限制访问的文件中,避免泄露。但是,私钥必须可以被 nginx 的主进程读取(读权限),私钥也可以与证书存储在同一文件中:

ssl_certificate            www.example.com.cert;
ssl_certificate_key     www.example.com.cert;

在这种情况下,文件访问权限也应该受到限制。虽然证书和密钥存储在一个文件中,但只有证书会发送给客户端。

指令 ssl_protocols 和 ssl_ciphers 可以用于将连接限制为仅包含 SSL/TLS 的强版本和密码。默认情况下,nginx 使用“ssl_protocols TLSv1 TLSv1.1 TLSv1.2”和“ssl_ciphers HIGH:!aNULL:!MD5”,因此通常不需要显式配置它们。

HTTPS 服务器优化

SSL 操作会消耗额外的 CPU 资源。在多处理器系统上,应该运行多个工作进程(worker processes),不少于可用 CPU 内核的数量。最占用 CPU 的操作是 SSL 握手。有两种方法可以最大限度地减少每个客户端的这些操作数量:

  • 第一个是通过启用保持连接(keepalive)以通过一个连接发送多个请求,即连接重用

  • 第二个是重用 SSL 会话参数以避免并行和后续连接的 SSL 握手

会话存储在共享的 SSL 会话缓存中,并由 ssl_session_cache 指令配置。一兆字节的缓存包含大约 4000 个会话,可以通过使用 ssl_session_timeout 指令来增加它。以下是针对具有 10 MB 共享会话缓存的多核系统优化的示例配置:

worker_processes auto;

http {
    ssl_session_cache   shared:SSL:10m;
    ssl_session_timeout 10m;

    server {
        listen              443 ssl;
        server_name         www.example.com;
        keepalive_timeout   70;

        ssl_certificate     www.example.com.crt;
        ssl_certificate_key www.example.com.key;
        ssl_protocols       TLSv1 TLSv1.1 TLSv1.2;
        ssl_ciphers         HIGH:!aNULL:!MD5;
        ...

SSL 证书链

某些浏览器可能会抱怨由知名证书颁发机构签署的证书,而其他浏览器可能会接受该证书而不会出现问题。发生这种情况是因为颁发机构已使用中间证书对服务器证书进行签名,该中间证书不存在于随特定浏览器分发的知名可信证书颁发机构的证书库中。在这种情况下,权威机构提供了一组链接的证书,这些证书应该连接到签名的服务器证书。服务器证书必须出现在组合文件中的链式证书之前:

$ cat www.example.com.crt bundle.crt > www.example.com.chained.crt

生成的文件应用于 ssl_certificate 指令:

server {
    listen              443 ssl;
    server_name         www.example.com;
    ssl_certificate     www.example.com.chained.crt;
    ssl_certificate_key www.example.com.key;
    ...
}

如果服务器证书和 bundle 的连接顺序错误,nginx 将无法启动并显示错误信息:

SSL_CTX_use_PrivateKey_file(" ... /www.example.com.key") failed
   (SSL: error:0B080074:x509 certificate routines:
    X509_check_private_key:key values mismatch)

因为 nginx 已尝试将私钥与捆绑包的第一个证书而不是服务器证书一起使用。

浏览器通常会存储它们收到的中间证书,这些证书由受信任的机构签署。因此,经常使用的浏览器可能已经拥有所需的中间证书,并且可能不会抱怨没有链接包发送的证书。为了确保服务器发送完整的证书链,可以使用 openssl 命令行实用程序,例如:

$ openssl s_client -connect www.godaddy.com:443
...
Certificate chain
 0 s:/C=US/ST=Arizona/L=Scottsdale/1.3.6.1.4.1.311.60.2.1.3=US
     /1.3.6.1.4.1.311.60.2.1.2=AZ/O=GoDaddy.com, Inc
     /OU=MIS Department/CN=www.GoDaddy.com
     /serialNumber=0796928-7/2.5.4.15=V1.0, Clause 5.(b)
   i:/C=US/ST=Arizona/L=Scottsdale/O=GoDaddy.com, Inc.
     /OU=http://certificates.godaddy.com/repository
     /CN=Go Daddy Secure Certification Authority
     /serialNumber=07969287
 1 s:/C=US/ST=Arizona/L=Scottsdale/O=GoDaddy.com, Inc.
     /OU=http://certificates.godaddy.com/repository
     /CN=Go Daddy Secure Certification Authority
     /serialNumber=07969287
   i:/C=US/O=The Go Daddy Group, Inc.
     /OU=Go Daddy Class 2 Certification Authority
 2 s:/C=US/O=The Go Daddy Group, Inc.
     /OU=Go Daddy Class 2 Certification Authority
   i:/L=ValiCert Validation Network/O=ValiCert, Inc.
     /OU=ValiCert Class 2 Policy Validation Authority
     /CN=http://www.valicert.com//emailAddress=info@valicert.com
...

注意:使用 SNI 测试配置时,指定 -servername 选项很重要,因为 openssl 默认不使用 SNI。

在此示例中,www.GoDaddy.com 服务器证书 #0 的主题 (s) 由颁发者 (i) 签名,该颁发者本身是证书 #1 的主题;服务器证书 #1 的主题的颁发者本身又是证书 #2 的主题;证书 #2 由知名发行人 ValiCert, Inc. 签署,其证书存储在浏览器的内置证书库中。

如果尚未添加证书包,则只会显示服务器证书 #0。

单个 HTTP/HTTPS 服务器

可以配置一个服务器来处理 HTTP 和 HTTPS 请求:

server {
    listen              80;
    listen              443 ssl;
    server_name         www.example.com;
    ssl_certificate     www.example.com.crt;
    ssl_certificate_key www.example.com.key;
    ...
}

在 0.7.14 之前,无法为单个侦听套接字选择性地启用 SSL,如上所示。 只能使用 ssl 指令为整个服务器启用 SSL,因此无法设置单个 HTTP/HTTPS 服务器。增加了 listen 指令的 ssl 参数来解决这个问题,因此不鼓励在现代版本中使用 ssl 指令。

基于名称的 HTTPS 服务器

配置两个或多个 HTTPS 服务器侦听单个 IP 地址时会出现一个常见问题:

server {
    listen          443 ssl;
    server_name     www.example.com;
    ssl_certificate www.example.com.crt;
    ...
}

server {
    listen          443 ssl;
    server_name     www.example.org;
    ssl_certificate www.example.org.crt;
    ...
}

使用此配置,浏览器将接收默认服务器的证书,即 www.example.com,无论请求的服务器名称如何。这是由 SSL 协议行为引起的,SSL 连接是在浏览器发送 HTTP 请求之前建立的,nginx 不知道请求的服务器的名称。因此,它可能只提供默认服务器的证书。

解决该问题的最古老、最可靠的方法是为每个 HTTPS 服务器分配一个单独的 IP 地址:

server {
    listen          192.168.1.1:443 ssl;
    server_name     www.example.com;
    ssl_certificate www.example.com.crt;
    ...
}

server {
    listen          192.168.1.2:443 ssl;
    server_name     www.example.org;
    ssl_certificate www.example.org.crt;
    ...
}

具有多个名称的 SSL 证书

还有其他方法允许在多个 HTTPS 服务器之间共享单个 IP 地址。 然而,它们都有其缺点。 一种方法是在 SubjectAltName 证书字段中使用具有多个名称的证书,例如 www.example.com 和 www.example.org。但是,SubjectAltName 字段长度是有限的。

另一种方法是使用带有通配符名称的证书,例如 *.example.org。通配符证书保护指定域的所有子域,但仅在一个级别上。此证书匹配 www.example.org,但不匹配 example.org 和 www.sub.example.org。这两种方法也可以结合使用。证书可能在 SubjectAltName 字段中包含精确名称和通配符名称,例如 example.org 和 *.example.org。

最好在配置的 http 级别放置一个具有多个名称的证书文件及其私钥文件,以在所有服务器中继承它们的单个内存副本:

ssl_certificate     common.crt;
ssl_certificate_key common.key;

server {
    listen          443 ssl;
    server_name     www.example.com;
    ...
}

server {
    listen          443 ssl;
    server_name     www.example.org;
    ...
}

服务器名称指示

在单个 IP 地址上运行多个 HTTPS 服务器的更通用的解决方案是 TLS 服务器名称指示扩展(SNI,RFC 6066),它允许浏览器在 SSL 握手期间传递请求的服务器名称。因此,服务器将知道哪个 它应该用于连接的证书。目前大多数现代浏览器都支持 SNI,但某些旧的或特殊的客户端可能不会使用它。

SNI 中只能传递域名,但是如果请求包含文字 IP 地址,某些浏览器可能会错误地将服务器的 IP 地址作为其名称传递。人们不应该依赖这一点。

为了在 nginx 中使用 SNI,它必须在构建 nginx 二进制文件的 OpenSSL 库以及它在运行时动态链接到的库中都得到支持。如果 OpenSSL 使用配置选项“--enable-tlsext”构建,则从 0.9.8f 版本开始支持 SNI。自 OpenSSL 0.9.8j 起,此选项默认启用。如果 nginx 是使用 SNI 支持构建的,那么在使用“-V”开关运行时 nginx 将显示:

$ nginx -V
...
TLS SNI support enabled
...

但是,如果启用 SNI 的 nginx 动态链接到不支持 SNI 的 OpenSSL 库,则 nginx 会显示警告:

nginx was built with SNI support, however, now it is linked
dynamically to an OpenSSL library which has no tlsext support,
therefore SNI is not available

兼容性

  • 自 0.8.21 和 0.7.62 以来,SNI 支持状态已由“-V”开关显示。

  • 从 0.7.14 开始支持 listen 指令的 ssl 参数。在 0.8.21 之前,它只能与默认参数一起指定。

  • 从 0.5.23 开始支持 SNI。

  • 从 0.5.6 开始支持共享 SSL 会话缓存。

  • 版本 1.9.1 及更高版本:默认 SSL 协议为 TLSv1、TLSv1.1 和 TLSv1.2(如果 OpenSSL 库支持)。

  • 版本 0.7.65、0.8.19 及更高版本:默认 SSL 协议为 SSLv3、TLSv1、TLSv1.1 和 TLSv1.2(如果 OpenSSL 库支持)。

  • 版本 0.7.64、0.8.18 及更早版本:默认 SSL 协议为 SSLv2、SSLv3 和 TLSv1。

  • 版本 1.0.5 及更高版本:默认 SSL 密码为“HIGH:!aNULL:!MD5”。

  • 版本 0.7.65、0.8.20 及更高版本:默认 SSL 密码为“HIGH:!ADH:!MD5”。

  • 版本 0.8.19:默认 SSL 密码为“ALL:!ADH:RC4+RSA:+HIGH:+MEDIUM”。

  • 版本 0.7.64、0.8.18 及更早版本:默认 SSL 密码为“ALL:!ADH:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP”。

说说我的看法
全部评论(
没有评论
关于
本网站专注于 Java、数据库(MySQL、Oracle)、Linux、软件架构及大数据等多领域技术知识分享。涵盖丰富的原创与精选技术文章,助力技术传播与交流。无论是技术新手渴望入门,还是资深开发者寻求进阶,这里都能为您提供深度见解与实用经验,让复杂编码变得轻松易懂,携手共赴技术提升新高度。如有侵权,请来信告知:hxstrive@outlook.com
公众号