配置 HTTPS 服务器

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

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

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 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 TLSv1.1 TLSv1.2 TLSv1.3”和“ssl_ciphers HIGH:!aNULL:!MD5”,因此通常不需要显式配置它们。请注意,这些指令的默认值已更改过几次。

HTTPS 服务器优化

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

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 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: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//[email protected]
...

在使用 SNI 测试配置时,务必指定 -servername 选项,因为 openssl 默认不使用 SNI。

在此示例中,www.GoDaddy.com 服务器证书 #0 的主体 (“s”) 由其颁发者 (“i”) 签名,而该颁发者本身是证书 #1 的主体,该证书由其颁发者签名,而该颁发者本身是证书 #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 服务器

在配置两个或多个在单个 IP 地址上监听的 HTTPS 服务器时,会出现一个常见问题。

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;
    ...
}

服务器名称指示

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

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

为了在 nginx 中使用 SNI,nginx 构建时所使用的 OpenSSL 库以及运行时动态链接到的库都必须支持 SNI。如果 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