必须启用 proxy_ssl_server_name on 并显式配置 proxy_ssl_name "域名",同时 proxy_pass 使用域名、proxy_set_header Host 与之严格一致,四者协同才能确保 TLS 握手时 SNI 域名匹配,避免 502 错误。

要让 Nginx 在 location 块中正确反代到外部 HTTPS 服务并避免 SNI 域名不匹配,核心是确保 TLS 握手时向上游发送的 Server Name 与后端期望的一致。这不是加一两个指令就能解决的事,关键在于四点协同:启用 SNI 扩展、指定准确的 SNI 值、透传匹配的 Host 头、且 proxy_pass 必须用域名。
必须开启 proxy_ssl_server_name 并配对 proxy_ssl_name
proxy_ssl_server_name on 是开关,不加它,Nginx 根本不会在 TLS 握手时发送 SNI 字段;但光开这个没用,SNI 字段填什么,由 proxy_ssl_name 决定:
- 若外部服务域名和客户端请求域名一致(如用户访问
app.example.com,你反代到api.example.com),直接写proxy_ssl_name $host; - 若经过 CDN 或网关且原始 Host 被覆盖(比如 CDN 回源时把 Host 改成
origin.internal),改用proxy_ssl_name $http_x_forwarded_host;,前提是 CDN 透传了X-Forwarded-Host头 - 禁止写
proxy_ssl_name $host不加引号或写成变量形式——该指令只接受字符串字面量,必须带英文双引号,例如proxy_ssl_name "api.example.com";
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;),但proxy_ssl_name仍需在location中显式指定为字符串"api.example.com" - 若上游必须走内网 IP,又需要特定 SNI,则必须配合
proxy_ssl_name显式赋值,不能依赖解析结果
Host 请求头要和 SNI 域名严格一致
不少外部服务(如云 API 网关、Istio、部分 SaaS 后端)不仅看 SNI,还会校验 Host 请求头。两者不一致时,可能跳过证书匹配直接拒绝:
- 设
proxy_set_header Host $host;—— 适用于原始 Host 可信场景 - 设
proxy_set_header Host "api.example.com";—— 适用于所有流量固定指向同一后端,且需规避客户端篡改 Host 的情况 - 避免混用:不要一边用
$host做 SNI,一边用"static.com"做 Host,否则易触发 403 或 502
验证是否真正生效
配置 reload 后不能只看 Nginx 是否启动成功,得确认 SNI 值确实发出去了:
- 在上游服务(如另一台 Nginx 或云厂商控制台日志)中查找类似
client sent server name "api.example.com"的记录 - 用 OpenSSL 模拟握手:
openssl s_client -connect api.example.com:443 -servername api.example.com -showcerts,观察返回证书的 CN/SAN 是否匹配 - 抓包比对:在 Nginx 机器上执行
tcpdump -i any port 443 -w sni-test.pcap,Wireshark 中过滤tls.handshake.type == 1,检查 Client Hello 的 Server Name Indication 字段内容

















