TLS 握手并不是仅仅协商出协议版本那么简单,更关键的一步是双方从各自支持的 Cipher Suites 列表中找到一个共同项。Nginx 作为服务端,会把自己配置的 ssl_ciphers 列表与客户端 ClientHello 中的列表取交集,再按顺序选择第一个。这个顺序直接影响最终使用的是 AES-GCM 还是已经过时的 3DES,也会影响是否具备前向保密。因此,ssl_ciphers 不是随便粘贴一段就能一劳永逸的配置,它需要跟随 OpenSSL 版本和业务兼容性目标调整。

ssl_ciphers 指令控制哪些算法进入握手
ssl_ciphers 指令位于 Nginx 的 server 或 http 块中,用来声明服务器愿意使用的 TLS 1.2 及以下版本的加密套件。每个套件由密钥交换、签名、对称加密和哈希算法组合而成,例如 ECDHE-RSA-AES128-GCM-SHA256 表示使用 ECDHE 做密钥交换、RSA 做证书签名、AES-128-GCM 做对称加密、SHA256 做伪随机函数。Nginx 会严格按照这里书写的先后顺序进行匹配,所以配置顺序本身就是一种优先级策略。
如果完全不写 ssl_ciphers,Nginx 会采用当前 OpenSSL 库的默认密码列表。这看起来很省事,但不同操作系统、不同 OpenSSL 版本的默认值差异很大:有的可能仍然包含 3DES,有的可能把 CBC 模式排在 GCM 前面,有的则直接关闭了 TLS 1.2 以下的所有算法。对于线上业务来说,依赖发行版默认值很容易导致安全基线漂移,也会让测试结果在不同环境中不一致。显式配置一套经过甄别的加密套件,是更可控的做法。
# 查看当前 Nginx 编译使用的 OpenSSL 版本 nginx -V 2>&1 | grep -o 'OpenSSL [0-9.]*'
还需要注意一个非常常见的误区:从 Nginx 1.19.4 开始支持 TLS 1.3 后,ssl_ciphers 并不能控制 TLS 1.3 的加密套件。TLS 1.3 使用独立的 Ciphersuites 配置,默认由 OpenSSL 自动选择,而且不再支持 RSA 密钥交换、CBC 模式等旧算法。因此,想让站点同时配置好 TLS 1.2 和 TLS 1.3,重点是把 ssl_protocols 限定在安全版本,而不是往 ssl_ciphers 里添加 TLS 1.3 的套件名。
可直接使用的两套推荐配置
如果业务主要面向近几年的浏览器和操作系统,建议直接使用下面的现代配置。它只保留 AEAD 算法,也就是 GCM 和 ChaCha20-Poly1305,完全不包含 CBC 模式。AEAD 算法把加密和完整性校验合并在一起,能有效避免 CBC 模式历史上出现过的 Padding Oracle、Lucky13 等时序攻击。同时,所有套件都以 ECDHE 开头,这意味着密钥交换过程具备前向保密,即使服务器证书私钥未来泄露,过去抓取的流量也无法被解密。
# 现代配置:仅 TLS 1.2 / TLS 1.3,仅 AEAD 套件 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 off;
有些场景仍然需要兼顾较旧的 Android、Windows 7 或早期 Java 客户端,这些客户端可能不支持 ChaCha20,也不支持较新的 ECDSA 证书。此时可以保留一部分 CBC 套件作为回退,但要放在 GCM 和 ChaCha20 之后,并且只保留 ECDHE 密钥交换,不要加入 RSA-AES 这类没有前向保密的套件。下面是一套兼容性稍好的配置,仍然不建议开启 TLS 1.0 和 TLS 1.1。
# 兼容配置:允许部分 CBC 套件回退 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:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA384'; ssl_prefer_server_ciphers on;
对比两套配置可以发现,推荐的套件数量并不多。很多教程会列出一长串算法,把 OpenSSL 支持的所有套件都堆进去,这并不会提高兼容性,反而会让客户端有概率协商到一个弱算法。真正影响兼容性的,往往是协议版本、证书类型和套件名称是否对应。例如配置了 ECDHE-ECDSA 但服务器用的是 RSA 证书,那么这个套件永远不会被选中;反之亦然。
几个容易踩坑的配置细节
ssl_prefer_server_ciphers 是另一个经常被误用的指令。在 TLS 1.2 及以下版本中,当它设置为 on 时,服务器会优先按照自己的套件顺序选择,而不是按客户端顺序。很多旧配置推荐打开它,是为了强制使用服务器认为更安全的算法。但现在更推荐在仅使用 AEAD 套件时将其关闭,让客户端顺序参与决策,因为客户端通常会把 ChaCha20 放在移动设备优先位置,这对没有 AES 硬件加速的手机更友好。如果服务器强制打开了 ssl_prefer_server_ciphers on,反而可能让移动设备放弃 ChaCha20 而选择较慢的 AES-GCM。
另一个隐蔽的问题是 ssl_protocols 与 ssl_ciphers 的配合。即使你把加密套件配置得再干净,只要 ssl_protocols 还包含 TLSv1 或 TLSv1.1,就仍然可能被扫描器标记为存在旧协议风险。从安全基线角度看,TLS 1.0 和 1.1 已不具备继续使用的价值,应该直接移除。如果业务确实存在老旧客户端,建议通过独立的子域名或网关来隔离,而不是在主站上长期保留弱协议。
# 验证是否还能通过 TLS 1.0 建立连接 openssl s_client -connect ipipp.com:443 -tls1 </dev/null >/dev/null 2>&1; echo $?
还有一点需要特别说明:TLS 1.3 不再支持 ssl_prefer_server_ciphers,该指令在 TLS 1.3 握手中会被忽略。TLS 1.3 的握手设计已经默认由客户端优先,服务器只负责从不多的 AEAD 套件中选择一个,因此不存在旧的服务器优先策略。说人话就是,如果你的 Nginx 同时开启了 TLS 1.3 和 TLS 1.2,ssl_prefer_server_ciphers 只对 TLS 1.2 部分生效,并不会影响 TLS 1.3 的连接。
验证最终生效的套件列表
配置写完之后,不能只凭感觉判断,应该用工具实际握手一次。最简单的办法是使用 openssl s_client 指定 TLS 版本和套件,看看目标服务器是否接受。例如下面这条命令可以测试 ECDHE-RSA-AES128-GCM-SHA256 是否真的被服务器选中。如果输出中能看到对应的 Cipher,说明配置已经生效;如果返回 handshake failure,则需要检查证书类型、协议版本和 OpenSSL 支持情况。
# 测试单个 TLS 1.2 加密套件是否能协商成功 openssl s_client -connect ipipp.com:443 -tls1_2 -cipher ECDHE-RSA-AES128-GCM-SHA256 </dev/null | grep Cipher
如果需要进行更全面的扫描,可以使用 nmap 的 ssl-enum-ciphers 脚本。它会枚举服务器支持的所有 TLS 版本和套件,并给出安全等级标注。对于核心业务,建议定期执行一次,关注是否出现 CBC、3DES、RC4 或匿名套件。也可以使用 testssl.sh 之类的工具生成完整报告,但需要注意把测试域名替换成实际域名,本文示例统一使用 ipipp.com。
最终的验证目标是让服务器只暴露介于 TLS 1.2 和 TLS 1.3 之间的安全套件,并且每个套件都有前向保密能力。如果测试结果中出现了 RSA-AES 或 DES-CBC3-SHA,说明 ssl_ciphers 可能没有被正确加载,或者存在其他 server 块继承了旧配置。Nginx 的配置覆盖规则是按块继承的,务必确认没有在更高层级留下旧的 ssl_ciphers 定义。
维护加密套件不是一次性的工作。随着旧设备逐渐淘汰,你应该逐步收敛 CBC 套件,把 ChaCha20 和 ECDHE-ECDSA 放在更靠前的位置。每次升级 Nginx 或 OpenSSL 后,也建议重新跑一次 openssl s_client 检查,确认新版本的默认行为没有意外覆盖你的显式配置。把 ssl_ciphers、ssl_protocols 和 ssl_prefer_server_ciphers 三者放在一起理解,比单纯复制一串算法名称更可靠。
Nginx ssl_ciphers加密套件TLS安全配置修改时间:2026-09-21 07:32:07