在HTTPS服务的安全配置中,加密套件的协商顺序经常被管理员忽略。默认情况下,TLS握手由客户端先提交一份它支持的算法列表,服务端从中挑选一个,但如果配置不当,选择逻辑可能偏向客户端的排序,导致服务端启用了一些强度偏弱的算法。Nginx的ssl_prefer_server_ciphers指令就是用来解决这个问题的,它强制按照服务端配置的算法顺序进行匹配,把主动权牢牢掌握在自己手里。

一、TLS握手时加密算法是如何协商的
要理解这个指令的作用,得先弄清楚TLS握手的基本过程。客户端发起连接时会发送ClientHello消息,里面带有一个CipherSuites列表,按客户端期望的程度从高到低排列,比如ECDHE-RSA-AES256-GCM-SHA384、ECDHE-RSA-AES128-GCM-SHA256等。服务端收到后会返回ServerHello,告知最终选定的套件。
问题的关键在于服务端怎么选。如果服务端配置为遵循客户端的偏好,那么只要列表里的算法服务端支持,就会按客户端排的顺序取第一个。这种模式下,攻击者可以通过伪造一个只支持弱算法的恶意客户端,诱导服务端使用较弱的套件,这也是所谓降级攻击的一种思路。而开启ssl_prefer_server_ciphers后,OpenSSL会反转遍历顺序,优先按服务端ssl_ciphers中定义的顺序去客户端列表里寻找匹配项,客户端的排序不再起决定作用。
举个例子,服务端配置了两个算法A和B,A更安全但性能稍差。客户端列表里B排在前面。如果遵循客户端偏好,最终协商结果是B;开启服务端优先后,由于A在服务端列表中排第一且客户端也支持,最终结果就是A。安全性由服务端掌控,这正是很多安全基线(如PCI DSS、等保要求)推荐的做法。
二、ssl_prefer_server_ciphers的配置方法
这条指令的语法非常简单,只有一个开关值,可以放在http、server块中。完整的安全配置示例如下:
server {
listen 443 ssl;
server_name www.ipipp.com;
ssl_certificate /etc/nginx/ssl/server.crt;
ssl_certificate_key /etc/nginx/ssl/server.key;
# 开启服务端算法优先
ssl_prefer_server_ciphers on;
# 限定协议版本
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;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
}
需要注意ssl_ciphers的写法。多个算法之间用冒号分隔,排序就是优先级顺序。推荐把支持前向保密(ECDHE系列)的套件放在前面,禁用RSA密钥交换的老套件和CBC模式的算法。这里还有一个容易踩的坑:TLSv1.3的套件不遵循ssl_ciphers指令,它有独立的协商逻辑,因此即使你在列表里没写TLS13开头的套件,TLS 1.3连接依然可以建立,这不是配置失效。
修改配置后记得用nginx -t检查语法再执行nginx -s reload,平滑重载后新配置才会生效。验证方式可以用openssl命令行工具直接测试实际协商出的算法:
openssl s_client -connect www.ipipp.com:443 -tls1_2 </dev/null 2>/dev/null | grep "Cipher" # 输出示例: # New, TLSv1.2, Cipher is ECDHE-RSA-AES128-GCM-SHA256
如果输出的Cipher和ssl_ciphers列表中排第一且客户端支持的算法一致,说明服务端优先已经生效。也可以借助Mozilla的SSL Configuration Generator生成配置模板,再结合自己环境的OpenSSL版本微调。
三、开启后可能遇到的问题与排查
开启服务端优先并不会主动禁用任何算法,它只改变选择顺序,所以一般不会直接导致客户端无法连接。真正容易出兼容性问题的是ssl_ciphers列表本身写得过于激进。比如有的管理员直接照抄了一套只含ECDHE套件的列表,而某些老旧的Java 6客户端、Windows XP上的IE浏览器只支持RSA交换,握手就会直接失败,报handshake failure错误。
排查这类问题的思路是先在Nginx的错误日志中开启debug级别,观察握手失败时客户端提交的套件列表和服务端的匹配过程。日志里如果出现"no shared cipher",基本可以确定是双方算法没有交集。解决办法要么在列表末尾追加兼容性套件作为兜底,要么推动客户端升级。从安全角度看,淘汰不支持前向保密的老客户端通常是更值得的选择。
另一个实践建议是配合ssl_protocols一起收紧。既然算法选择权已经交给服务端,就应该把TLSv1.0和TLSv1.1一并禁掉,只保留TLSv1.2和TLSv1.3。 ssl_prefer_server_ciphers on、ssl_protocols TLSv1.2 TLSv1.3、一份精心排序的ssl_ciphers列表,这三者组合起来才是一套完整的服务端主导的安全策略。配置完成后可以用ssllabs的在线检测跑一次评分,正常情况下应该能达到A以上等级,同时观察客户端兼容性报告,确认业务所需的客户端类型都还在支持范围内。
Nginxssl_prefer_server_ciphersSSL/TLS配置修改时间:2026-09-06 00:48:38