编译自:
[configuring_https_servers][1]
[1]: http://nginx.org/en/docs/http/configuring_https_servers.html
目录:
- 简介
- HTTPS 服务器优化
- SSL 证书链
- 一个 HTTP/HTTPS 服务器
- Name-based HTTPS 服务器
- 多个 server 共享一个 SSL 证书
- Server Name Indication
- 兼容性
简介
配置 HTTPS 服务,必须为 listen 指令加上 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 TLSv1.1 TLSv1.2;
ssl_ciphers HIGH:!aNULL:!MD5;
...
}
服务器证书将被发送给每个连接服务器的客户端。私钥必须在服务器端保存,应该为私钥添加严格的访问限制,nginx 主进程必须对其有读权限。私钥可以和证书存放在同一个文件中:
ssl_certificate www.example.com.cert;
ssl_certificate_key www.example.com.cert;
这个文件当然也应该加上严格的权限设置。虽然证书和私钥被存放在同一个文件中,但只有证书会被发送给客户端。
使用 ssl_protocols 指令 和 ss_chiphers 指令,可设置加密连接使用高安全性的协议版本以及加密性强的算法(SSL/TLS协议)。nginx 默认使用 “ssl_protocols TLSv1 TLSv1.1 TLSv1.2” 以及 “ssl_ciphers HIGH:!aNULL:!MD5”,它们分别指定了默认的协议版本和加密算法,所以这些算法不需要显式地指定。要注意的是,这两个指令的默认值已经多次发生改变(详见 “兼容性” 小节)。
[listen][2] 指令
[2]: http://nginx.org/en/docs/http/ngx_http_core_module.html#listen
[server][3] 指令
[3]: http://nginx.org/en/docs/http/ngx_http_core_module.html#server
[server certificate][4] 指令
[4]: http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_certificate
[private key][5] 指令
[5]: http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_certificate_key
[ssl_protocols][6] 指令
[6]: http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_protocols
[ss_chiphers][7] 指令
[7]: http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_ciphers
HTTPS 服务器优化
处理 SSL 连接会消耗额外的 CPU 资源。在多处理器系统上,应设置对应CPU核心个数的 worker 进程。(参考:worker_processes)
建立 SSL 连接的握手阶段是最消耗 CPU 的,有两种方法可最小化建立每个 SSL 连接所需要的握手操作次数:
- 第一是启用 keepalive 连接保持。启动连接保持,可以在一个已建立的 SSL 连接上处理多个请求
- 第二是重用 SSL 会话参数,使并行的或者后续的连接不再需要进行 SSL 握手。
SSL 连接的会话参数被保存在 SSL 会话缓存中,该缓存被所有的 worker 进程共享,可使用 ssl_session_cache 指令对其进行配置。1MB SSL 会话可容纳约 4000 个会话。
默认的缓存超时为 5 分钟,可使用 ssl_session_timeout 指令进行调整。
下面是一个 SSL 优化配置样例,假设系统拥有的 CPU 核心总数为 10个,为其配置 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_session_cache][9]
[9]: http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_session_cache
[ssl_session_timeout][10]
[10]: http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_session_timeout
SSL 证书链
有时会出现这样的情况,对一个由知名 CA 签发的证书,一些浏览器发出警告,而另一些浏览器会接受。这是因为签发该证书的 CA 使用了一个 intermediate certificate 签发证书,这个 intermediate certificate 没有包含在跟随浏览器一起分发的证书库中。为应对这个问题,CA 提供了 a bundle of chained certificate ,可将该证书与你的服务器证书合并成一个文件。在这个文件中,服务器的证书必须位于 chained certificate 的前面:
$ 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;
...
}
如果顺序颠倒了,把服务器证书放在了 chained certificate 的后面,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 发现服务器的私钥和 chained certificate 的第一个证书不匹配造成的。
当浏览器收到 intermediate certificates 时,一般都会将它存储下来。所以浏览器可能在第一次收到 intermediate certificates 时发出警告,但存储下来之后再次收到时就不会发出警告了。
要确定一个 web 服务器是否发送了完整的 certificate chain,可使用 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
...
Server certificate
-----BEGIN CERTIFICATE-----
...
在此例中的 Certificate chain 中,#0号证书的对象 (“s”) 的证书颁发者是 (“i”),#0证书的 (“i”) 同时又是 #1 号证书的对象 (“s”);#1号证书颁发者 (“i”) 是 #2号证书的对象 (“s”),#2号证书的颁发者 (“i”) 是知名 CA “ValiCert, Inc”,这个 CA 的证书是存储在随浏览器分发的内建证书库中的。
如果服务器发送给客户端的证书没有包含 certificate chain,上面的信息只会显示 #0 号服务器证书。
一个 HTTP/HTTPS 服务器
建立一个可同时处理 HTTP 和 HTTPS 请求的 web 服务器:
server {
listen 80;
listen 443 ssl;
server_name www.example.com;
ssl_certificate www.example.com.crt;
ssl_certificate_key www.example.com.key;
...
}
Note:
nginx 0.7.14 版之前,不支持像上面这样单独将某个监听套接字设置为 SSL 连接。
只能在 server 区块中使用 ssl {on|off} 指令,定义整个 server 提供 HTTPS 服务,
因此不能设置可同时处理 HTTP/HTTPS 请求的 server 区块。现在不建议在新版本的 nginx
中使用 ssl 指令,建议使用 ssl 参数。
Name-based HTTPS 服务器
基于名称的 HTTPS 服务器。
子目录:
- 概念讲解
- 多个 server 共享一个 SSL 证书
- Server Name Indication
概念讲解
如何设置监听于一个 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;
...
}
虽然在这样的配置中为两个 server 设置了不同的证书,但是当使用浏览器访问该 web 站点时,无论访问的主机名是 www.example.com 还是 www.example.org,浏览器都将收到同一个服务器证书:服务器的默认证书。在这里的默认证书是 www.example.com.crt。
这是由 SSL 协议的行为所决定的。SSL 连接建立于 TCP/IP 连接之上,SSL 连接在握手的阶段,会收到由 nginx 服务器发送的服务器证书,SSL 连接建完成之时,浏览器还没有发送 HTTP 请求给 nginx,因此 nginx 无法在建立 SSL 连接时得知浏览器所请求的是哪一个虚拟主机,因此,nginx 只能发送默认的服务器证书给浏览器。
对于这个问题,最老的方法,也是最 robust 的方法,是为每个 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;
...
}
多个 server 共享一个 SSL 证书
有多种方式可实现在多个 HTTPS servers 之间共享一个 IP 地址,但这些方法都有各自的缺点。
一种方法是在证书的 SubjectAltName 字段中设置多个主机名,比如设置两个主机名:www.example.com 和 www.example.org。缺点是 SubjectAltName 字段的长度是有限制的。
另一种方法是在证书中设置“通配主机名”,比如 *.example.org,但它只能匹配一个名字区域的主机名,比如,它不能匹配 example.org 和 www.sub.example.org。
以上两种方法可以结合使用,也就是在证书的 SubjectAltName 字段中同时包含多个 “准确主机名” 和 “通配主机名”。比如同时包含:example.org 和 *.example.org。
对于这种在多个 HTTPS servers 之间共享一个 IP 地址的应用场景,最好在配置中,将服务器的证书和私钥放到 http 区块中,使得所有的 server 区块可继承该配置:
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;
...
}
Server Name Indication
对于实现在多个 HTTPS servers 之间共享一个 IP 地址,或者说基于同一个 IP 地址运行多个 HTTPS server,一种更为通用的解决方案是使用 TLS Server Name Indication extension (SNI, RFC 6066)。
通过 SNI 可允许浏览器在与 web 服务器进行 SSL 握手的阶段,将所请求的 server name 传递给服务器,这样服务器就能够为这个 SSL 连接选择对应的证书。
但是 SNI 对浏览器的版本有要求,目前支持 SNI 的浏览器版本如下:
Opera 8.0;
MSIE 7.0 (but only on Windows Vista or higher);
Firefox 2.0 and other browsers using Mozilla Platform rv:1.8.1;
Safari 3.2.1 (Windows version supports SNI on Vista or higher);
and Chrome (Windows version supports SNI on Vista or higher, too).
Note:
在 SNI 中只能传递 domain names(域名)。如果一个访问请求中包含有 IP 地址,
一些浏览器会错误地将服务器的 IP 地址当做所请求的主机名传递给服务器。因此,不能
完全依赖 SNI。
为了在 nginx 中使用 SNI,要求两种函数库支持 SNI:一是 nginx 编译时使用的 OpenSSL 库,二是 nginx 在运行时动态链接的库。OpenSSL 从 0.9.8f 版开始支持 SNI(要求 OpenSSL 在编译时使用了 “--enable-tlsext” 选项)。从 0.9.8j 版开始,该选择是默认的选项。如果 nginx 编译进了对 SNI 的支持,那么使用 nginx -V 命令查看时,可看到:
$ nginx -V
...
TLS SNI support enabled
...
附上译者的测试:
[root@lamp1 ~]# nginx -V
nginx version: nginx/1.10.1
built by gcc 4.4.7 20120313 (Red Hat 4.4.7-17) (GCC)
built with OpenSSL 1.0.1e-fips 11 Feb 2013
TLS SNI support enabled
...
如果 SNI-enabled 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 开始,可使用 nginx -V 显示 SNI 支持状态信息。
从 0.7.14 开始,nginx 支持在 listen 指令中使用 ssl 参数,而且在 0.8.21 之前,ssl 参数只能和 default 参数一起使用。
从 0.5.32 开始支持 SNI。
从 0.5.6 开始支持 SSL 会话缓存。
从 1.9.1 开始,默认的 SSL 协议为 TLSv1, TLSv1.1, and TLSv1.2 (if supported by the OpenSSL library)
从 0.7.65, 0.8.19 开始,到 1.9.1 之前,默认的 SSL 协议为 SSLv3, TLSv1, TLSv1.1, and TLSv1.2 (if supported by the OpenSSL library)。
0.7.64, 0.8.18 及之前,默认的 SSL 协议为 SSLv2, SSLv3, and TLSv1。
从 1.0.5 开始,默认的 SSL 加密算法为 “HIGH:!aNULL:!MD5”。
0.7.65, 0.8.20 之后,1.0.5 之前,默认的 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”。
written by Igor Sysoev
edited by Brian Mercer
版权信息:
本文编译自 nginx.org 的部分,遵循其原来的 licence 声明: 2-clause BSD-like license