根本原因是TLS握手时SNI字段未正确传递:Nginx默认用proxy_pass中的IP或内网域名作为SNI值,导致后端无法匹配证书而断连;必须成对配置proxy_ssl_server_name on和proxy_ssl_name(动态用$host或静态写死),并确保与Host头一致。

问题不在 Nginx 转发逻辑,而在于 TLS 握手第一帧就已“失联”——客户端请求 shop-a.com,Nginx 却向后端发了 10.0.0.100 或 backend.internal 作为 SNI 值,导致后端无法选出对应证书,直接断连或返回错误证书。浏览器报安全警告,Nginx 报 502、证书校验失败或静默超时。
先确认是不是 SNI 问题
别急着改配置,先验证根本原因:
- 查 Nginx 错误日志:
/var/log/nginx/error.log,搜索关键词:SSL_do_handshake() failed、tlsv1 alert handshake failure(alert number 40)、certificate verify failed - 直连对比:用
curl -v https://your-backend-domain.com直连后端,确认服务本身可用;再用curl -v https://your-nginx-domain.com访问代理层,看是否失败 - 抓包验证(可选):在 Nginx 机器上运行
tcpdump -i any port 443 -w sni-test.pcap,Wireshark 打开后过滤tls.handshake.type == 1,检查 Client Hello 是否含server_name扩展,且值是否为你期望的域名
核心配置必须成对生效
proxy_ssl_server_name on 只是打开开关,proxy_ssl_name 才决定发什么。二者缺一不可,且必须放在 location 块内:
- 动态场景(推荐):
proxy_ssl_server_name on;proxy_ssl_name $host;
✓ $host 已标准化(小写、去端口),比 $http_host 更可靠 - 静态上游(如固定 API 网关):
proxy_ssl_server_name on;proxy_ssl_name "api.example.com";
✓ 必须加英文双引号,值需与后端证书的 CN 或 SAN 完全一致 - 用了
upstream块?注意:
proxy_ssl_server_name 和 proxy_ssl_name 都不能写在 upstream 内,必须放在引用它的 location 中
CDN 或网关回源导致 $host 失真
Cloudflare、阿里云全站加速等中间层常重写 Host 头为源站名(如 origin.example.com),此时 $host 就不再是用户原始域名,SNI 必然错配:
- 在 CDN 控制台开启「透传原始 Host」功能,确保它带上
X-Forwarded-Host头 - Nginx 中改用:
proxy_ssl_name $http_x_forwarded_host; - 顺手补上:
proxy_set_header X-Forwarded-Host $host;
方便后端识别真实租户,也利于日志审计
验证 SNI 是否真按预期发出
配置写对 ≠ 生效。必须实测 TLS 握手时发出的 SNI 值:
- 在上游服务器(如后端 Nginx)开启 debug 日志:
error_log /var/log/nginx/error.log debug;
重启后触发一次请求,搜索日志中client sent server name字样,确认值是否为你原始域名 - 命令行快速验证:
openssl s_client -connect 10.0.0.100:443 -servername shop-a.com -showcerts
观察返回证书的 Subject CN 或 SAN 是否匹配 shop-a.com - 检查 OpenSSL 版本:
nginx -V 2>&1 | grep -o 'OpenSSL [0-9.]\+'
必须 ≥ 1.0.2h;若显示 TLS SNI support disabled,需重新编译 Nginx 并启用 enable-tl***t 参数











