SSL_do_handshake()失败本质是协议层第一帧解析失败,需通过debug日志、access_log上下文及openssl验证精准定位:HTTP打HTTPS端口、HTTPS打HTTP端口或proxy_pass协议错配。

这个错误不是 SSL 配置写错了,而是客户端和 Nginx 之间根本没对上“语言”——协议版本不匹配或连错了端口。监控它不能只盯着报错文字,得把日志变成可定位的线索。
抓准 error_log 中的真实握手失败点
Nginx 默认 error_log 不输出握手细节,看到的 “ssl3_get_record:wrong version number” 只是表象。必须启用 debug 级 SSL 日志才能还原真实原因:
- 在出问题的 server 块里加一行:error_log /var/log/nginx/ssl_debug.log debug;
- 确认 Nginx 编译时含 --with-debug(运行
nginx -V 2>&1 | grep -o with-debug验证) - 重启 Nginx 后复现请求,打开 ssl_debug.log 查找形如
SSL_do_handshake() failed (SSL: error:1408F10B:SSL routines:ssl3_get_record:wrong version number)的完整行
注意:错误码 1408F10B 和后面字符串共同决定问题类型,不是泛泛的“SSL 错误”,而是“协议层第一帧就解析失败”。
用 access_log 补全上下文,区分三类典型场景
单看 error_log 无法判断是谁发错了请求。配合增强的 access_log 才能锁定来源:
- 定义日志格式:log_format tls_detail '$remote_addr - $remote_user [$time_local] "$request" $status $body_bytes_sent "$http_referer" "$http_user_agent" "$ssl_protocol" "$ssl_cipher" "$ssl_server_name";'
- 在对应 server 块中启用:access_log /var/log/nginx/tls_access.log tls_detail;
查日志时重点关注这三类组合:
-
HTTP 请求打到 HTTPS 端口:access_log 中 request 字段以
GET http://开头,但 server 是 listen 443;error_log 出现该错误 → 客户端硬编码了 443 端口却发 HTTP -
HTTPS 请求打到 HTTP 端口:access_log 中 request 字段为空或乱码(如
"\x16\x03\x01...),server 是 listen 80 且无 ssl 配置 → 浏览器/curl 把 HTTPS 请求发到了 80 端口 -
上游代理协议错配:error_log 明确提示
while SSL handshaking to upstream,且 upstream 地址是https://xxx:80→ proxy_pass 写成 https 却指向了 HTTP 端口
快速验证协议与端口是否真正匹配
不用猜,用命令直接验证目标服务实际支持什么:
- 查本地 Nginx 监听配置:ss -tlnp | grep :443 确认 443 是否真由 nginx 监听且启用了 SSL
- 测上游服务是否真跑 HTTPS:openssl s_client -connect target-host:443 -servername example.com 2>/dev/null | head -5,如果返回 HTTP 响应(如
HTTP/1.1 200 OK)说明它其实是 HTTP 服务 - 检查 proxy_pass 地址:proxy_pass https://127.0.0.1:8081 这种写法必须确保 8081 端口后端真提供 HTTPS,否则就是协议错配
常见陷阱:proxy_ssl_protocols 默认是 TLSv1.2 TLSv1.3,但上游若只支持 TLSv1.0,就得显式加 proxy_ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3;
设置告警阈值,避免被噪音淹没
这个错误高频出现往往意味着配置批量出错,不是偶发故障:
- 用 logrotate 或 filebeat 按小时切分 ssl_debug.log,统计每小时出现该错误的次数
- 设定告警阈值:5 分钟内超过 10 次 触发告警(说明不是个别用户输错 URL,而是配置大面积失效)
- 告警内容必须带上下文:错误码 + 对应的 access_log 行(含 remote_addr 和 $request),方便立刻定位是哪类请求、哪个客户端 IP 或哪个 upstream
不复杂但容易忽略。


















