4、Nginx TLS配置:SSL证书部署、TLS协议版本选择、加密套件配置与优化
好,咱们进入实战环节。前面讲了证书链、密钥格式这些基础,现在得真刀真枪地配置Nginx了。说实话,Nginx的TLS配置坑不少,我这些年踩过的雷,今天一次性给你说清楚。
4.1 证书与密钥文件部署
先把证书文件放对位置。我个人习惯在 /etc/nginx/ssl/ 下按域名建目录,比如:
/etc/nginx/ssl/example.com/
├── fullchain.pem # 服务器证书 + 中间证书链
├── privkey.pem # 私钥(权限600)
└── dhparam.pem # DH参数文件(可选,但推荐)
注意权限。私钥一旦泄露,整个TLS就形同虚设。我见过有人把私钥设成644,所有人都能读——这跟把密码贴在服务器外壳上没区别。
- 私钥文件:
chmod 600 privkey.pem,属主为root - 证书文件:
chmod 644 fullchain.pem,Nginx工作进程需要读取 - 父目录:
chmod 755 /etc/nginx/ssl/example.com/ - 定期检查:
find /etc/nginx/ssl -type f -perm /o+r -name "privkey*"
然后看Nginx配置段。核心指令就这几条:
server {
listen 443 ssl http2;
server_name example.com;
ssl_certificate /etc/nginx/ssl/example.com/fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/example.com/privkey.pem;
ssl_dhparam /etc/nginx/ssl/example.com/dhparam.pem;
# 其他配置...
}
这里有个细节:ssl_certificate 指向的是 完整证书链,不是单张服务器证书。我曾经遇到一个客户,只配了服务器证书没带中间证书,结果Android手机全报错——因为Android不缓存中间证书,需要服务端一次性下发完整链。
openssl s_client -connect example.com:443 -showcerts
# 检查输出中是否包含至少2个证书(服务器证书 + 中间CA证书)
4.2 TLS协议版本选择
协议版本这块,现在没什么好纠结的。TLS 1.0和1.1已经正式退役,主流浏览器都标记为不安全。我建议直接关闭它们。
配置很简单:
ssl_protocols TLSv1.2 TLSv1.3;
为什么只开这两个?
| 协议版本 | 状态 | 安全风险 | 兼容性 |
|---|---|---|---|
| SSLv3 / TLSv1.0 | ❌ 已废弃 | POODLE、BEAST攻击 | 仅老旧客户端(IE6/8、Android 4.x以下) |
| TLSv1.1 | ⚠️ 已废弃 | 不支持AEAD套件,Lucky13攻击 | 极少使用 |
| TLSv1.2 | ✅ 推荐 | 安全(正确配置下) | 所有现代客户端 |
| TLSv1.3 | ✅ 强烈推荐 | 最安全,握手仅1-RTT | 2016年后发布的浏览器/库 |
你可能会问:那老客户端怎么办?我的建议是——别管了。TLS 1.0和1.1的安全性已经千疮百孔,为了不到0.1%的流量去冒被攻击的风险,不值当。如果真有业务需要,单独开一个端口给老协议,做好监控和隔离。
4.3 加密套件配置与优化
加密套件配置是TLS里最容易被搞砸的部分。我见过太多人直接复制网上的配置,结果要么太宽松(包含RC4、CBC模式),要么太激进(只开ECDHE,导致老旧客户端连不上)。
先看我的推荐配置:
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers on;
这条配置我用了好几年,没出过兼容性问题。来拆解一下:
- 优先使用ECDHE密钥交换:支持PFS(完美前向保密),即使私钥泄露,历史会话也无法被解密
- AEAD模式优先:AES-GCM和ChaCha20-Poly1305都是认证加密,一次完成加密和完整性校验,性能好、安全
- 保留DHE作为兜底:有些老旧客户端不支持ECDHE,DHE-RSA可以兼容
- 彻底排除CBC模式:AES-CBC + HMAC-SHA1的组合有Lucky13、Padding Oracle等攻击风险
为什么 ssl_prefer_server_ciphers on 很重要?因为客户端会在ClientHello里列出自己支持的套件列表,如果服务端不强制自己的优先级,攻击者可以诱导客户端选择弱套件进行降级攻击。我曾在渗透测试中利用这个漏洞成功破解过TLS流量——嗯,从那以后我再也不敢关这个选项了。
4.4 性能优化:会话复用与OCSP Stapling
配置安全了,还得考虑性能。TLS握手开销不小,尤其是首次连接。这里有两个关键优化点。
4.4.1 SSL会话缓存与票据
会话复用可以让客户端在短时间内重新连接时跳过完整的TLS握手。Nginx支持两种方式:
# 方法一:基于共享内存的会话缓存
ssl_session_cache shared:SSL:10m; # 10MB可存储约40000个会话
ssl_session_timeout 10m; # 会话超时时间
# 方法二:基于会话票据(推荐)
ssl_session_tickets on;
ssl_session_ticket_key /etc/nginx/ssl/ticket.key;
我个人更推荐会话票据方式。为什么呢?因为共享内存方案在Nginx多worker进程间需要同步,而且重启Nginx后所有缓存失效,所有客户端都得重新握手。会话票据把状态加密后发给客户端保存,服务端无状态,重启也不影响。
- 票据密钥文件需要定期轮换(建议每月一次)
- 多台Nginx服务器必须共享同一个ticket.key,否则客户端在不同节点间切换时会强制重新握手
- 密钥文件生成:
openssl rand 48 > /etc/nginx/ssl/ticket.key
4.4.2 OCSP Stapling
OCSP Stapling是让Nginx主动去CA查询证书吊销状态,然后把结果“订”在TLS握手过程中发给客户端。这样客户端就不需要自己去连接CA的OCSP服务器了——既提升性能,又保护隐私(CA不知道谁在访问你的网站)。
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 1.1.1.1 valid=300s;
resolver_timeout 5s;
配置完记得验证:
openssl s_client -connect example.com:443 -status
# 输出中应包含 "OCSP Response Status: successful"
如果没生效,最常见的原因是防火墙没放行Nginx到CA OCSP服务器的出站连接。我遇到过一家金融客户,安全策略严格到Nginx连外网都不行,结果OCSP Stapling一直失败——最后只能通过内部代理转发。
4.5 完整配置模板与验证
把上面所有内容整合成一个完整的TLS配置段:
server {
listen 443 ssl http2;
server_name example.com;
# 证书与密钥
ssl_certificate /etc/nginx/ssl/example.com/fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/example.com/privkey.pem;
ssl_dhparam /etc/nginx/ssl/example.com/dhparam.pem;
# 协议与套件
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers on;
# 会话复用
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
ssl_session_tickets on;
ssl_session_ticket_key /etc/nginx/ssl/ticket.key;
# OCSP Stapling
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 1.1.1.1 valid=300s;
resolver_timeout 5s;
# HSTS(可选,但强烈推荐)
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
# 其他业务配置...
}
配置完一定要做全面验证。我每次改完TLS配置都会跑一遍这个检查清单:
- 语法检查:
nginx -t - 协议与套件扫描:
nmap --script ssl-enum-ciphers -p 443 example.com - SSL Labs测试:https://www.ssllabs.com/ssltest/(目标A+)
- 证书链完整性:
openssl s_client -connect example.com:443 -showcerts - OCSP Stapling:
openssl s_client -connect example.com:443 -status - HSTS头:
curl -I https://example.com | grep -i strict
最后,送你一张本章的知识体系图,方便你对照检查自己的配置:
嗯,配置TLS就像搭积木——证书、协议、套件、优化,每一块都得放对位置。别想着一步到位,先跑通,再优化,最后用工具验证。我每次上线新服务,至少会花半小时跑一遍上面的验证清单。别嫌麻烦,出一次安全事件,代价远不止这半小时。