必须启用proxy_ssl_server_name on并配proxy_ssl_name指定sni域名,同时proxy_pass用域名、host头与sni严格一致,四者协同才能解决tls握手失败。

解决反代 HTTPS 后端时因域名不匹配导致的 TLS 握手失败,核心在于让 Nginx 在与上游建立 TLS 连接时,正确发送 SNI(Server Name Indication)并确保该值与后端期望的域名一致。这不是单个指令能搞定的事,而是四个配置项必须协同生效。
必须启用 proxy_ssl_server_name 并配准 SNI 值
Nginx 默认禁用 SNI 扩展,即 Client Hello 中不携带 server_name 字段。即使你 proxy_pass 写的是域名,也不代表它会把这个域名作为 SNI 发送出去——必须显式开启:
- 在 location 块中写 proxy_ssl_server_name on;(不能放在 upstream 或 http 块里)
- 紧接着必须配 proxy_ssl_name "目标域名"; ——注意:必须是带英文双引号的字符串字面量,不支持未加引号的变量(如 $host)
- 若所有请求都指向同一后端,可写死:
proxy_ssl_name "api.example.com"; - 若需动态适配客户端 Host,且可信,可用:
proxy_ssl_name $host;(但需确保 $host 不含端口)
proxy_pass 必须使用域名,不能只写 IP
Nginx 不会从客户端请求中自动提取域名去补全 SNI;它只认 proxy_pass 指令里写的值。一旦写成 proxy_pass https://10.10.20.30/;,SNI 就为空,后端无法选证。
- 正确写法:
proxy_pass https://api.example.com/; - 若用 upstream,upstream 块内可写
server api.example.com:443;,但 location 中仍需显式指定proxy_ssl_name "api.example.com"; - 若上游必须走内网 IP(如 192.168.1.100),则
proxy_ssl_name更关键,必须手动设为对应域名,否则握手必败
Host 请求头要与 SNI 域名严格一致
很多后端(如 Istio、云 API 网关、Traefik)不仅依赖 SNI 选证书,还会校验 Host 请求头。两者不一致时,可能直接返回 403 或 502。
- 若 SNI 是
"api.example.com",则 Host 头也应设为相同值:proxy_set_header Host "api.example.com"; - 避免混用:不要 SNI 用 $host,Host 头却写死为 static.com;也不要 SNI 写死,Host 却用 $http_host(可能带端口)
- 若需保留原始 Host 且可信,可统一用:
proxy_ssl_name $host;+proxy_set_header Host $host;
验证是否真正生效
配置 reload 后不能只看 Nginx 是否启动成功,需实测验证:
- 用
openssl s_client -connect your-nginx-ip:443 -servername api.example.com模拟客户端发起带 SNI 的请求 - 检查 Nginx error 日志,确认不再出现
SSL_do_handshake() failed ... sslv3 alert handshake failure - 抓包验证 Client Hello 中是否含 Server Name 扩展字段,且值为你设定的域名
- 后端 access log 应开始收到请求,而非完全无记录











