Nginx错误日志里出现 SSL_do_handshake() failed,不等于服务器证书一定坏了。这只是握手阶段失败的总入口,真正有用的信息在后面的OpenSSL原因、客户端地址、server和失败时间。同一句前缀可能对应协议版本不匹配、没有共同密码套件、SNI选错证书、客户端证书失败,也可能只是互联网上的扫描器发来畸形请求。
这类问题最怕看到错误就放宽TLS版本或降低密码套件。兼容性可能暂时提高,安全边界却被悄悄改变。更合理的顺序是先确认真实用户是否受影响,再用相同域名、SNI、协议和网络路径复现,最后只修改已被证据指向的配置。

保存完整错误而不是只看前缀
先在问题时间段读取完整日志:
sudo grep -n 'SSL_do_handshake() failed' /var/log/nginx/error.log | tail -50
sudo journalctl -u nginx --since '30 minutes ago' --no-pager
sudo nginx -T 2>&1 > /tmp/nginx-effective.conf
关注括号中的OpenSSL错误,例如unsupported protocol、no shared cipher、certificate verify failed、bad key share或SNI相关提示。客户端IP和出现频率也很关键:大量随机地址只失败一次,更像扫描;同一地区、同一设备群持续失败,才更像真实兼容问题。
日志级别不应长期调到debug。需要短时提高时,要确认Nginx是否带debug支持、日志磁盘空间和隐私范围,观察结束后恢复原级别。
用正确的SNI域名复现
直接请求服务器IP无法完整复现基于域名的HTTPS,因为TLS握手会在HTTP请求之前选择证书。使用OpenSSL显式发送SNI:
openssl s_client -connect 203.0.113.10:443 -servername example.com -showcerts
再用curl固定解析到目标IP,验证握手与HTTP响应:
curl -vk --resolve example.com:443:203.0.113.10 https://example.com/
-k 只适合诊断,它会跳过证书验证,不能作为修复配置。完成连通性定位后,应去掉 -k 再测试信任链、域名和有效期。
多CDN、多负载均衡或灰度IP环境要逐个入口测试。只测试DNS当前返回的一个地址,可能漏掉某台仍加载旧证书或旧配置的节点。
检查协议版本是否有交集
Nginx官方当前默认启用TLS 1.2和TLS 1.3,但真实生效范围取决于Nginx、OpenSSL版本和配置继承。查看:
nginx -V 2>&1
openssl version -a
sudo nginx -T 2>&1 | grep -n 'ssl_protocols'
分别强制协议测试:
openssl s_client -connect example.com:443 -servername example.com -tls1_2
openssl s_client -connect example.com:443 -servername example.com -tls1_3
旧客户端只支持已禁用协议时会失败,但重新开放TLS 1.0或1.1并不是默认答案。应先确认受影响客户端是否仍在支持范围、是否能升级,以及业务合规要求。安全协议降级必须经过明确风险评估。
核对密码套件与OpenSSL能力
TLS 1.2与TLS 1.3的密码套件配置方式不同。配置过窄、复制了不兼容的安全模板,或升级OpenSSL后算法被禁用,都可能出现没有共同套件。
openssl ciphers -v
sudo nginx -T 2>&1 | grep -nE 'ssl_ciphers|ssl_conf_command|ssl_ecdh_curve'
不要把网上的一长串cipher直接覆盖生产配置。先从错误原因与客户端能力确认缺少哪类算法,再检查证书密钥类型、曲线和服务器版本。RSA证书、ECDSA证书与不同客户端的组合也可能影响结果。
修改后应使用多个真实客户端和独立TLS测试工具验证,而不是只用服务器本机最新版OpenSSL。最新版成功只证明现代客户端路径可用。
检查默认证书与server选择
TLS握手发生时,Nginx先根据监听地址、端口和SNI选择虚拟主机。客户端不发送SNI,或SNI不匹配任何 server_name,会落到该listen组合的默认server。
sudo nginx -T 2>&1 | grep -nE 'listen .*443|server_name|ssl_certificate'
openssl s_client -connect example.com:443 -noservername
默认证书过期、文件缺失或配置了 ssl_reject_handshake on,都会影响无SNI或未知域名请求。ssl_reject_handshake 可用于明确拒绝未知主机,但日志中的失败可能正是预期行为,不应为了让日志安静而关闭。
证书与私钥必须匹配,完整链也要正确。配置加载成功不代表所有中间证书都能被客户端建立信任,应同时检查实际下发链。
区分源站握手与上游握手
错误日志中的 while SSL handshaking 通常描述客户端与Nginx的入站握手;Nginx代理到HTTPS上游失败时,常伴随 SSL_do_handshake() failed 与 while SSL handshaking to upstream。两者配置入口不同。
上游HTTPS要检查 proxy_ssl_server_name、proxy_ssl_name、信任证书和上游域名:
proxy_ssl_server_name on;
proxy_ssl_name backend.example.com;
这只是配置位置示例。上游按SNI选择证书时,未发送正确名称可能拿到默认证书;开启验证后,名称与信任链也必须匹配。不要通过永久关闭 proxy_ssl_verify 隐藏证书问题。
客户端证书验证失败怎么查
站点启用mTLS后,服务端会要求客户端提供证书。证书缺失、过期、签发CA不匹配或吊销检查失败,都会在握手阶段中断。
sudo nginx -T 2>&1 | grep -nE 'ssl_client_certificate|ssl_verify_client|ssl_crl|ssl_ocsp'
使用测试客户端时要明确提供客户端证书和私钥,并保护文件权限。不要把生产私钥粘贴到命令历史、工单或第三方检测网站。
仅部分API路径需要客户端认证时,应重新评估TLS层和应用层的设计。握手发生在具体HTTP路径可见之前,不能期待Nginx先看到URL再决定本次连接要不要客户端证书。
判断是否只是扫描与异常流量
公网443端口会收到旧协议、随机字节、明文HTTP和漏洞扫描。日志量不大、没有真实用户报障、成功请求指标正常时,这些握手失败通常不需要放宽配置。
可以按客户端IP、错误原因和时间窗口聚合,检查是否集中在少数来源或呈现固定扫描特征。封禁前要确认没有共享出口、监控探针或合作方设备,避免把真实用户一起拦截。
需要在不同Nginx与OpenSSL版本上复现时,可以在 萤光云 建立隔离环境,或在按小时计费的 LightNode 上搭建脱敏测试站。只使用测试证书和虚构域名,不要复制生产私钥。
安全修改与灰度验证
配置改动前保存 nginx -T 输出和当前证书指纹,修改后先做语法检查:
sudo nginx -t
sudo systemctl reload nginx
sudo journalctl -u nginx --since '5 minutes ago' --no-pager
平滑加载后,新连接进入新worker,已有连接可能继续由旧worker处理。多节点站点先灰度一台,通过域名固定解析验证,再逐步扩展,避免一次性让所有入口使用未经验证的TLS策略。
回滚内容应包括Nginx配置、证书文件、私钥权限和自动续期钩子。只回滚配置但保留错误证书文件,问题仍会继续。
修复后怎样验收
验收至少覆盖TLS 1.2、TLS 1.3、主域名、备用域名、无SNI预期行为、证书链和真实受影响客户端。多入口站点逐个IP验证,并观察握手成功率和错误原因分布。
openssl s_client -connect example.com:443 -servername example.com -status
curl -v --resolve example.com:443:203.0.113.10 https://example.com/
合格的修复应说明是哪一方在哪个握手阶段不兼容,修改没有扩大不必要的协议范围,并且真实客户端与安全基线同时通过。
常见问题
日志里有SSL_do_handshake() failed就代表用户打不开吗?
不一定。扫描器和不受支持的旧客户端也会产生该日志,要结合真实请求、来源和错误原因判断。
直接开放TLS 1.0能解决吗?
可能让个别旧设备连接,但会降低安全性且可能违反合规要求。应先升级客户端或明确业务例外,不应默认降级。
为什么访问IP时证书不对,访问域名却正常?
基于SNI的虚拟主机会按域名选择证书。直接访问IP可能落到默认server,这通常不代表目标域名配置错误。


