配置 HTTPS 服务器

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

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

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

    可以使用 ssl_protocols 和 指令来限制连接,使其仅包括 SSL/TLS 的版本和密码。默认情况下,nginx 使用版本为 ssl_protocols TLSv1 TLSv1.1 TLSv1.2,密码为 ssl_ciphers HIGH:!aNULL:!MD5,因此通常不需要配置它们。请注意,这些指令的默认值已经被 更改 多次。

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

    1. worker_processes auto;
    2. http {
    3. ssl_session_cache shared:SSL:10m;
    4. ssl_session_timeout 10m;
    5. server {
    6. listen 443 ssl;
    7. server_name www.example.com;
    8. keepalive_timeout 70;
    9. ssl_certificate www.example.com.crt;
    10. ssl_certificate_key www.example.com.key;
    11. ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    12. ssl_ciphers HIGH:!aNULL:!MD5;
    13. ...

    SSL 证书链

    某些浏览器可能会不承认由知名证书颁发机构签发的证书,而其他浏览器可能会接受该证书。之所以发生这种情况,是因为颁发机构已经在特定浏览器分发了一个中间证书,该证书不存在于知名可信证书颁发机构的证书库中。在这种情况下,权威机构提供了一系列链式证书,这些证书应该与已签名的服务器证书相连。服务器证书必须出现在组合文件中的链式证书之前:

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

    ssl_certificate 指令中使用生成的文件:

    如果服务器证书与捆绑的链式证书的相连顺序错误,nginx 将无法启动并显示错误消息:

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

    浏览器通常会存储他们收到的中间证书,这些证书由受信任的机构签名,因此积极使用这些存储证书的浏览器可能已经具有所需的中间证书,不会发生不承认没有链接捆绑发送的证书的情况。可以使用 openssl 命令行工具来确保服务器发送完整的证书链,例如:

    1. $ openssl s_client -connect www.godaddy.com:443
    2. ...
    3. Certificate chain
    4. 0 s:/C=US/ST=Arizona/L=Scottsdale/1.3.6.1.4.1.311.60.2.1.3=US
    5. /1.3.6.1.4.1.311.60.2.1.2=AZ/O=GoDaddy.com, Inc
    6. /serialNumber=0796928-7/2.5.4.15=V1.0, Clause 5.(b)
    7. i:/C=US/ST=Arizona/L=Scottsdale/O=GoDaddy.com, Inc.
    8. /OU=http://certificates.godaddy.com/repository
    9. /serialNumber=07969287
    10. 1 s:/C=US/ST=Arizona/L=Scottsdale/O=GoDaddy.com, Inc.
    11. /OU=http://certificates.godaddy.com/repository
    12. /CN=Go Daddy Secure Certification Authority
    13. /serialNumber=07969287
    14. i:/C=US/O=The Go Daddy Group, Inc.
    15. /OU=Go Daddy Class 2 Certification Authority
    16. 2 s:/C=US/O=The Go Daddy Group, Inc.
    17. /OU=Go Daddy Class 2 Certification Authority
    18. i:/L=ValiCert Validation Network/O=ValiCert, Inc.
    19. /OU=ValiCert Class 2 Policy Validation Authority
    20. /CN=http://www.valicert.com//emailAddress=info@valicert.com
    21. ...

    在本示例中,www.GoDaddy.com 服务器证书 #0 的主体(“s”)由发行机构(“i”)签名,发行机构本身是证书 #1 的主体,是由知名发行机构 ValiCert, Inc. 签署的证书 #2 的主体,其证书存储在浏览器的内置证书库中。

    如果没有添加证书包(certificate bundle),将仅显示服务器证书 #0。

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

    1. server {
    2. listen 80;
    3. listen 443 ssl;
    4. server_name www.example.com;
    5. ssl_certificate www.example.com.crt;
    6. ssl_certificate_key www.example.com.key;
    7. ...
    8. }

    基于名称的 HTTPS 服务器

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

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

    1. server {
    2. listen 192.168.1.1:443 ssl;
    3. server_name www.example.com;
    4. ssl_certificate www.example.com.crt;
    5. ...
    6. }
    7. server {
    8. listen 192.168.1.2:443 ssl;
    9. server_name www.example.org;
    10. ...

    虽然还有其他方法允许在多个 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 级配置,以便在所有服务器中继承其单个内存副本:

    1. ssl_certificate common.crt;
    2. ssl_certificate_key common.key;
    3. server {
    4. listen 443 ssl;
    5. server_name www.example.com;
    6. ...
    7. }
    8. server {
    9. listen 443 ssl;
    10. server_name www.example.org;
    11. ...

    服务器名称指示

    在单个 IP 地址上运行多个 HTTPS 服务器的更通用的解决方案是 (SNI,RFC 6066),其允许浏览器在 SSL 握手期间传递所请求的服务器名称,因此,服务器将知道应该为此连接使用哪个证书。然而,SNI 对浏览器的支持是有限的。目前,它仅支持以下版本开始的浏览器:

    • Opera 8.0
    • MSIE 7.0(但仅在 Windows Vista 或更高版本)
    • Firefox 2.0 和使用 Mozilla Platform rv:1.8.1 的其他浏览器
    • Safari 3.2.1(支持 SNI 的 Windows 版本需要 Vista 或更高版本)
    • Chrome(支持 SNI 的 Windows 版本需要 Vista 或更高版本)

    只有域名可以在 SNI 中传递,然而,如果请求包含 IP 地址,某些浏览器可能会错误地将服务器的 IP 地址作为名称传递。不应该依靠这个。

    要在 nginx 中使用 SNI,其必须支持构建后的 nginx 二进制的 OpenSSL 库以及在可在运行时动态链接的库。自 0.9.8f 版本起(OpenSSL),如果 OpenSSL 使用了配置选项 --enable-tlsext 构建,是支持 SNI 的。自 OpenSSL 0.9.8j 起,此选项是默认启用。如果 nginx 是用 SNI 支持构建的,那么当使用 nginx -V 命令时 ,nginx 会显示:

    1. $ nginx -V
    2. ...
    3. TLS SNI support enabled
    4. ...

    但是,如果启用了 SNI 的 nginx 在没有 SNI 支持的情况下动态链接到 OpenSSL 库,那么 nginx 将会显示警告:

    • 从 0.8.21 和 0.7.62 起,SNI 支持状态通过 -V开关显示。
    • 从 0.7.14 开始,支持 listen 指令的 ssl 参数。在 0.8.21 之前,只能与 default 参数一起指定。
    • 从 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

    原文档