配置HTTPS服务器

HTTPS服务器优化
SSL证书链
单个HTTP/HTTPS服务器
基于名称的HTTPS服务器
     包含多个名称的SSL证书
     服务器名称指示 (SNI)
兼容性

要配置 HTTPS 服务器,必须在 server 块中,为 监听套接字 启用 ssl 参数,并指定 服务器证书私钥 文件的位置。

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.2 TLSv1.3;
    ssl_ciphers         HIGH:!aNULL:!MD5;
    ...
}

服务器证书是一个公共实体。它会发送给每个连接到服务器的客户端。私钥是一个安全实体,应存储在一个访问受限的文件中,但 nginx 的主进程必须可读。私钥也可以存储在与证书相同的文件中。

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

在这种情况下,文件访问权限也应受限。尽管证书和私钥存储在同一个文件中,但只有证书会发送给客户端。

指令 ssl_protocolsssl_ciphers 可用于将连接限制为仅包含 SSL/TLS 的强版本和密码。默认情况下,nginx 使用“ssl_protocols TLSv1.2 TLSv1.3”和“ssl_ciphers HIGH:!aNULL:!MD5”,因此通常无需明确配置它们。请注意,这些指令的默认值已更改多次。

HTTPS服务器优化

SSL 操作会消耗额外的 CPU 资源。在多处理器系统上,应运行多个worker 进程,数量不应少于可用的 CPU 核心数。最耗 CPU 的操作是 SSL 握手。有两种方法可以最大程度地减少每个客户端的这些操作次数:第一种是启用keepalive 连接,通过一个连接发送多个请求;第二种是重用 SSL 会话参数,以避免并行连接和后续连接的 SSL 握手。会话存储在 worker 进程共享的 SSL 会话缓存中,通过 ssl_session_cache 指令进行配置。一兆字节的缓存大约包含 4000 个会话。默认缓存超时时间为 5 分钟。可以使用 ssl_session_timeout 指令增加超时时间。以下是针对具有 10 兆字节共享会话缓存的多核系统优化的示例配置:

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.2 TLSv1.3;
        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;
    ...
}

如果服务器证书和捆绑证书的顺序连接错误,nginx 将无法启动并显示错误消息。

SSL_CTX_use_PrivateKey_file(" ... /www.example.com.key") failed
   (SSL: error:05800074:x509 certificate routines::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
...

在此示例中,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 指令;它已在 1.25.1 版本中移除。

基于名称的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.comwww.example.org。然而,SubjectAltName 字段的长度是有限的。

另一种方法是使用带有通配符名称的证书,例如 *.example.org。通配符证书可保护指定域的所有子域,但仅限于一个级别。此证书匹配 www.example.org,但不匹配 example.orgwww.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;
    ...
}

服务器名称指示 (SNI)

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

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

为了在 nginx 中使用 SNI,它必须同时得到构建 nginx 二进制文件时使用的 OpenSSL 库以及运行时动态链接到的库的支持。如果 OpenSSL 是使用配置选项构建的,则从 0.9.8f 版本开始支持 SNI。“--enable-tlsext”。从 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

兼容性

撰写:Igor Sysoev
编辑:Brian Mercer