文章目录
本节介绍如何在NGINX和NGINX Plus上配置HTTPS服务器。
1. 设置HTTPS服务器
要设置HTTPS服务器,请在nginx.conf文件的server块中将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或NGINX Plus服务器的每个客户端。私钥是一个安全实体,应存储在具有受限访问权限的文件中。但是,NGINX主进程必须能够读取该文件。另外,私钥可以与证书存储在同一文件中:
ssl_certificate www.example.com.cert;
ssl_certificate_key www.example.com.cert;
在这种情况下,限制对文件的访问非常重要。请注意,尽管在这种情况下证书和密钥存储在一个文件中,但是仅证书被发送到客户端。
ssl_protocols 和 ssl_ciphers 指令可用于要求客户机在建立连接时仅使用SSL/TLS的强版本和密码。
从1.9.1版开始,NGINX使用以下默认值:
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers HIGH:!aNULL:!MD5;
在旧密码的设计中有时会发现漏洞,我们建议在现代NGNIX配置中禁用它们(不幸的是,由于现有NGNX部署的向后兼容性,默认配置很难更改)。请注意,CBC模式密码可能容易受到许多攻击(特别是如CVE-2011-3389所述的BEAST攻击),我们建议不要使用SSLv3,因为POODLE攻击,除非您需要支持旧客户端。
示例
:
- 使用自签名的SSL证书。首先使用openssl命令生成证书:
# openssl req -x509 -nodes -days 36500 -newkey rsa:2048 -keyout /root/project/ssl/nginx.key -out /root/project/ssl/nginx.crt
得到 nginx.crt 和 nginx.key 两个文件,分别为证书和私钥。
- 确认构建nginx时包含http_ssl_module模块:
# /usr/local/nginx/sbin/nginx -V
nginx version: nginx/1.16.1
built by gcc 4.8.5 20150623 (Red Hat 4.8.5-28) (GCC)
built with OpenSSL 1.0.2k-fips 26 Jan 2017
TLS SNI support enabled
configure arguments: --prefix=/usr/local/nginx --with-http_ssl_module
- 配置站点文件,启用SSL配置。因为是示例,简单配置即可,所以将原nginx.conf文件中https部分的#注释去掉,并修改,结果如下:
server {
listen 443 ssl;
server_name example.com;
ssl_certificate /root/project/ssl/nginx.crt;
ssl_certificate_key /root/project/ssl/nginx.key;
ssl_session_cache shared:SSL:1m;
ssl_session_timeout 5m;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
location / {
root html;
index index.html index.htm;
}
}
- 重新加载nginx,打开浏览器查看证书(因为是自签名证书,只用于试验,所以会提示不安全。生产环境肯定要使用第三方机构签名的证书了):
2. HTTPS服务器优化
SSL操作会消耗额外的CPU资源。最耗CPU的操作是SSL握手。有两种方法可以最大程度地减少每个客户端执行这些操作的次数:
- 启用keepalive连接以通过一个连接发送多个请求
- 重用SSL会话参数以避免相应SSL握手和后续连接
会话存储在工作进程之间共享的SSL会话缓存中,并由ssl_session_cache指令配置。一兆字节的缓存包含大约4000个会话。默认的缓存超时为5分钟。可以使用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;
#...
}
}
3. 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尝试将私有密钥与捆绑包(bundle.crt)的第一个证书一起使用,而不是服务器证书。
浏览器通常存储接收到的中间证书,同时这些中间证书已经由受信任的权威机构签名。因此,经常使用的浏览器可能已经具有所需的中间证书,并且可能不会抱怨没有链式捆绑包发送的证书。为了确保服务器发送完整的证书链,可以使用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号证书的主体(subject,简写为s)被颁发者(简写为 i)签名,同时颁发者也是1号证书的主体。1号证书的颁发者同时也是2号证书的主体。最终,此证书由著名的颁发者ValiCert,Inc.签署,其证书存储在浏览器中。
如果尚未添加证书捆绑包,则仅显示服务器证书(0号证书)。
4. 单个HTTP / HTTPS服务器
通过在同一个虚拟服务器中放置一个带ssl参数的listen指令和一个不带ssl参数的listen指令,可以配置一个同时处理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;
#...
}
在NGINX版本0.7.13及更早版本中,无法为单个监听套接字选择性地启用SSL,如上所述。只能使用SSL指令为整个服务器启用SSL,因此无法设置单个HTTP/HTTPS服务器。为解决此问题,向listen指令添加了ssl参数。因此,在0.7.14及更高版本中不推荐使用ssl指令。
5. 基于名称的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;
#...
}
请注意,还有一些特定的HTTPS upstreams代理设置(proxy_ssl_ciphers, proxy_ssl_protocols, 和 proxy_ssl_session_reuse),可用于NGINX和upstream服务器之间的ssl微调。
5.1 具有多个名称的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;
#...
}
5.2 服务器名称指示
TLS服务器名称指示 (SNI)扩展(RFC 6066)是在单个IP地址上运行多个HTTPS服务器的更通用的解决方案,它允许浏览器在SSL握手期间传递请求的服务器名称。使用此解决方案,服务器将知道它应使用哪个证书进行连接。但是,SNI的浏览器支持有限。当前从以下浏览器版本开始受支持:
- Opera 8.0
- MSIE 7.0(但仅在Windows Vista或更高版本上)
- Firefox 2.0和其他使用Mozilla Platform rv:1.8.1的浏览器
- Safari 3.2.1(Windows版本在Vista或更高版本上支持SNI)
- Chrome(Windows版本在Vista或更高版本上也支持SNI)
在SNI中只能传递域名。但是,如果请求包含文本IP地址,一些浏览器会将服务器的IP地址作为其名称传递。最好不要依赖这个。
为了在NGINX中使用SNI,必须在构建NGINX二进制文件的OpenSSL库以及在运行时动态链接的库中都支持SNI。如果使用配置选项 --enable-tlsext 构建,则OpenSSL从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
参考文档
https://docs.nginx.com/nginx/admin-guide/security-controls/terminating-ssl-http/
https://www.jianshu.com/p/280de4af8c00