ssl握手失败主因是nginx反向代理https后端时sni与host头不一致或缺失;须显式配置proxy_ssl_name和proxy_set_header host为同一域名,并实测验证。

反向代理 HTTPS 后端时出现 SSL 握手失败,多数不是因为“本地证书不匹配”,而是 Nginx(或类似代理)在连接上游 HTTPS 服务时,未正确告诉对方“我要访问哪个域名”——即 SNI(Server Name Indication)和 Host 头不一致或缺失。真正的故障点常在代理配置,而非你本机的证书。
必须显式设置 proxy_ssl_name
当 proxy_pass 指向一个 HTTPS 域名(如 https://api.example.com/)时,Nginx 默认会用该域名作为 SNI 名称,通常没问题。但若你改用内网 IP(如 https://192.168.1.100/),SNI 就会变成 IP 地址——而绝大多数 HTTPS 服务不接受 IP 作为 SNI,直接拒连。
此时必须手动指定:
- proxy_ssl_name "api.example.com"; —— 强制 SNI 使用目标域名
- 该指令需放在 location 或 upstream 对应的 proxy 配置块中,仅靠 upstream server 行写域名不够
- 若 upstream 中用了 server api.example.com:443;,仍需在 location 里加这行,不能省略
Host 请求头必须与 SNI 严格一致
很多现代后端(Istio、Traefik、云 API 网关)不仅看 SNI,还会校验 HTTP Host 头。两者不一致,可能返回 403 或 502,表面像握手失败,实为应用层拦截。
正确做法是统一设为同一值:
- proxy_set_header Host "api.example.com";
- 避免混用:比如 SNI 写死为 api.example.com,Host 却用 $http_host(可能带端口或用户原始域名)
- 若你信任客户端 Host 并希望透传,可统一用 proxy_ssl_name $host; + proxy_set_header Host $host;,但需确保 $host 值始终合法且被后端认可
验证是否真正生效,别只看 Nginx reload 成功
配置重载后必须实测,否则容易误判:
- 用 OpenSSL 模拟真实客户端行为:
openssl s_client -connect your-nginx-ip:443 -servername api.example.com -showcerts - 观察输出中的 subject=CN=... 和 Verify return code;若返回 20(证书链不完整)或 42(hostname mismatch),说明 proxy_ssl_name 或证书本身有问题
- 同时抓包或在 upstream 侧启用日志,确认收到的 TLS ClientHello 中 SNI 字段和后续 HTTP 请求中的 Host 头是否均为预期值
其他常见干扰项
排除非核心配置问题:
- 后端服务是否真启用了 TLS?用 openssl s_client -connect 192.168.1.100:443 -servername api.example.com 直连测试,绕过 Nginx
- 后端证书是否覆盖该域名?用 openssl x509 -in cert.pem -text -noout | grep DNS 检查 SAN 列表
- Nginx 是否启用了 proxy_ssl_verify off;?临时关闭证书校验可快速定位是证书问题还是 SNI/Host 问题(生产环境勿长期关闭)











