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%的流量去冒被攻击的风险,不值当。如果真有业务需要,单独开一个端口给老协议,做好监控和隔离。

🔑 核心原则: 能用TLS 1.3就用TLS 1.3,退而求其次用TLS 1.2。其他版本一律关闭。

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配置都会跑一遍这个检查清单:

  1. 语法检查:nginx -t
  2. 协议与套件扫描:nmap --script ssl-enum-ciphers -p 443 example.com
  3. SSL Labs测试:https://www.ssllabs.com/ssltest/(目标A+)
  4. 证书链完整性:openssl s_client -connect example.com:443 -showcerts
  5. OCSP Stapling:openssl s_client -connect example.com:443 -status
  6. HSTS头:curl -I https://example.com | grep -i strict

最后,送你一张本章的知识体系图,方便你对照检查自己的配置:

Nginx TLS 配置知识体系 Nginx TLS 配置 证书与密钥部署 fullchain.pem privkey.pem (权限600) dhparam.pem TLS 协议版本 ✅ TLSv1.2 ✅ TLSv1.3 ❌ TLSv1.0/1.1 加密套件配置 ECDHE + AEAD 优先 排除CBC模式 prefer_server_ciphers on 性能优化 会话缓存 / 会话票据 OCSP Stapling HSTS 头 验证:SSL Labs A+ 目标

嗯,配置TLS就像搭积木——证书、协议、套件、优化,每一块都得放对位置。别想着一步到位,先跑通,再优化,最后用工具验证。我每次上线新服务,至少会花半小时跑一遍上面的验证清单。别嫌麻烦,出一次安全事件,代价远不止这半小时。