排查Nginx TLS故障需先明确握手方向:client→Nginx为服务端问题,SSL handshaking to upstream为Nginx作为客户端连后端失败;再结合OpenSSL错误码(如14209102表TLS版本不兼容)和证书实际参数(公钥类型、签名算法)精准定位。

排查 Nginx 中证书与 TLS 版本不兼容的故障,关键不是换证书或调参数,而是分清“谁在跟谁说话”、看清“协议协商卡在哪一步”。证书本身不会拒绝某个 TLS 版本,但证书签名算法、公钥类型、密钥交换机制和 TLS 版本是强耦合的——比如 ECDSA 证书在 TLS 1.2 下依赖特定签名算法,在 TLS 1.3 下又依赖不同密钥交换流程。错配时日志往往只报泛泛的 SSL_do_handshake() failed,真正线索藏在错误码和上下文里。
先锁定握手方向和失败角色
打开 /var/log/nginx/error.log,不要只盯失败行,重点看紧邻的上下文:
- 出现 while SSL handshaking, client: xxx.xxx.xxx.xxx → Nginx 是服务端,问题出在浏览器、App 或 Java 客户端连你这台服务器时,TLS 协商失败
- 出现 while SSL handshaking to upstream → Nginx 是客户端,问题出在它去连后端(如 Spring Boot、Kibana、云 API)时,对方 TLS 配置不匹配
方向错了,后续所有排查都跑偏。多数线上问题属于后者,但常被误判为“证书有问题”,白忙活。
抓 OpenSSL 错误码,定位协议层断点
每条失败日志后通常跟着十六进制错误码,它是精准诊断的钥匙:
- error:14094410 (handshake failure) → 常见于 SNI 缺失(尤其多域名后端)、密码套件无交集,或服务端要求客户端证书但未提供
-
error:14209102 (unsupported protocol) → TLS 版本不兼容,例如 Nginx 配了
ssl_protocols TLSv1.2 TLSv1.3,但后端只支持 TLSv1.1 -
error:1408F10B (wrong version number) → 后端实际没开 HTTPS(监听 HTTP 端口),Nginx 却用
https://去连;或客户端把 HTTPS 请求发到了 HTTP 端口 - error:141CF06C (bad key share) → TLS 1.3 密钥交换失败,常见于后端 OpenSSL 或 JDK 太老(如 JDK 8u221 以下不支持 X25519)
验证证书是否真能支撑目标 TLS 版本
证书文件名或 CA 类型不能代表实际能力,必须从证书内容读取真实约束:
- 检查叶子证书公钥类型:
openssl x509 -in fullchain.pem -noout -text | grep "Public Key Algorithm",确认是rsaEncryption还是id-ecPublicKey - 查看证书签名算法:
openssl x509 -in fullchain.pem -noout -text | grep "Signature Algorithm",RSA-PSS 和 ECDSA 在 TLS 1.2/1.3 中支持度不同 - 若用 ECDSA 证书,确保 Nginx 配置中启用了对应曲线:
ssl_ecdh_curve secp256r1;(Android 4–6 只认 prime256v1) - 若后端是 Java 应用,注意 Java 7/8 默认不支持 TLS 1.3,且对 ECDSA 签名验证有版本门槛(JDK 8u291+ 才完整支持)
针对性复现与最小验证
别靠猜,用工具主动触发并比对:
- 模拟老客户端:用
openssl s_client -connect your.com:443 -tls1_1 -servername your.com看是否握手成功 - 检查服务端实际支持哪些协议:
openssl s_client -connect your.com:443 -servername your.com -tls1_2 -cipher 'ALL:eNULL' 2>&1 | grep Cipher - 如果是 upstream 类型,直接 curl 测试后端真实协议:
curl -v https://backend-ip:port和curl -v http://backend-ip:port对比 - 启用 debug 日志临时确认协商细节:
error_log /var/log/nginx/ssl-debug.log debug;(排查完务必关掉)











