ssl_do_handshake() failed本质是nginx与上游或客户端tls握手中断,需先据日志中“to upstream”或“client: xxx”区分方向:前者查协议匹配、sni启用(proxy_ssl_server_name on)、会话复用;后者查证书链完整性、tls版本及密码套件兼容性。

“Client Abort”在 Nginx 日志中通常不是 SSL 握手失败的直接错误,而是连接被客户端主动中断的表象。真正需要关注的是 SSL_do_handshake() failed 及其后紧跟的 OpenSSL 错误码——它才揭示握手阶段到底卡在哪一步。排查核心不是看“abort”,而是精准定位日志里带 to upstream 或 client: 的上下文,并匹配错误码含义。
先分清方向:是 Nginx 连后端失败,还是客户端连 Nginx 失败?
这是所有排查的前提,方向错了全白忙:
- 日志出现 while SSL handshaking to upstream → 问题出在 Nginx 作为 HTTPS 客户端去调用后端服务(比如 proxy_pass https://xxx)
- 日志出现 while SSL handshaking, client: xxx.xxx.xxx.xxx(无 upstream 字样)→ 问题出在 Nginx 作为 HTTPS 服务端接收浏览器/APP 请求(server 块的 ssl 配置环节)
针对 “to upstream” 场景:重点查协议、SNI 和会话复用
这是线上最常见的一类,错误日志往往表现为 handshake failure、wrong version number 或 ccs received early:
- 验证 proxy_pass 协议是否真实匹配:后端实际监听 HTTP(如 8080 端口跑 Spring Boot),你就不能写 proxy_pass https://127.0.0.1:8080;反之,后端是 HTTPS,却写成 http://,也会因协议错位导致握手崩掉。用 curl -v http://ip:port 和 curl -v https://ip:port 分别测试确认
-
强制启用 SNI 并指定域名:当后端是多域名 HTTPS 服务(如 K8s Ingress、云 API、Nginx 多站点反代),必须加两行:
proxy_ssl_server_name on;
proxy_ssl_name "api.example.com";
同时补上 Host 头:proxy_set_header Host "api.example.com"; - 临时关闭会话复用排查干扰:默认 proxy_ssl_session_reuse on 可能在后端证书轮换或节点不一致时引发状态错乱。可先设为 off 观察是否还报 ccs received early
针对 “client: xxx” 场景:聚焦证书链、TLS 版本与密码套件
这类问题影响终端用户访问,需检查 Nginx 服务端配置完整性:
- 确保证书链完整且路径正确:ssl_certificate 必须指向含完整链的 PEM(域名证书 + 中间 CA);若单独配置中间证书,用 ssl_trusted_certificate 指向;用 openssl s_client -connect yourdomain.com:443 -showcerts 验证链是否可回溯到可信根
- 放宽 TLS 版本或检查客户端兼容性:若日志报 version too low 或 tlsv1 alert protocol version,说明客户端(如旧版 IE、嵌入式设备)支持的 TLS 版本低于 Nginx ssl_protocols 设置的最低值。可临时加入 TLSv1.1 测试,再逐步收紧
- 避免密码套件过度精简:ssl_ciphers 配置如只留 ECDHE-ECDSA 类套件,而客户端仅支持 RSA 密钥交换,就会触发 no shared cipher。建议用 Mozilla 推荐的 intermediate 配置作为起点,再按需调整
让日志真正“说话”:开启 debug 级 SSL 日志
默认 error_log 级别看不到握手细节。需主动启用高粒度日志:
- 在 http 或 server 块中添加:error_log /var/log/nginx/ssl_debug.log debug;
- 确认 Nginx 编译含 --with-debug(运行 nginx -V | grep with-debug 验证)
- 重启后,ssl_debug.log 中会出现类似:
SSL_do_handshake() failed (SSL: error:1417A0C1:SSL routines:tls_post_process_client_hello:no shared cipher)
直接对应到具体原因,无需猜测











