必须开启 proxy_ssl_server_name on 并动态设置 proxy_ssl_name,否则 Nginx 与后端微服务 TLS 握手必然失败;因默认不发 SNI,上游无法匹配多域名证书,导致证书校验失败或连接中断。

必须开启 proxy_ssl_server_name on 并配合动态 proxy_ssl_name,否则 Nginx 与后端微服务 TLS 握手必然失败——不是 502,就是证书校验报错。
为什么 proxy_ssl_server_name on 不开就根本连不上
Nginx 默认不发 SNI 字段,它会把 proxy_pass https://10.0.0.10:443 这种写法里的 IP 或字面主机名(如 backend.internal)直接当 SNI 值用。而微服务网关(比如 Spring Cloud Gateway、Traefik、或自建的 Nginx 集群)通常一个 IP 托管几十个租户域名,每个配了独立证书。没 SNI,上游只能返回默认证书或直接中断握手,日志里常见:
SSL_do_handshake() failedtlsv1 alert internal error-
certificate verify failed(即使你关了proxy_ssl_verify,部分网关仍拒接无 SNI 的连接)
proxy_ssl_name 必须用变量,不能写死
只开 proxy_ssl_server_name on 不够,SNI 字段内容得对上客户端真实请求的域名。微服务场景下,$host 是最稳妥的选择:
-
$host是 Nginx 解析后的 Host 头值:小写、不含端口、已去标准化,比$http_host更可靠 - 绝对不要写成
proxy_ssl_name "api.tenant-a.com";—— 微服务路由是动态的,硬编码等于给所有请求塞同一个 SNI - 若用
map做域名映射(如把tenant-a.example.com映射到a-gw.internal),则proxy_ssl_name应设为对应 map 变量,而非原始$host
CDN 或 API 网关回源时 $host 被覆盖怎么办
Cloudflare、阿里云全站加速、腾讯云 CDN 回源时默认重写 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;,方便下游微服务做租户识别 - 验证:用
curl -H "Host: api.tenant-b.com" http://your-nginx/api/模拟,再查上游 debug 日志是否收到client sent server name "api.tenant-b.com"
验证 SNI 是否真按预期发出
配置写对 ≠ 生效。必须确认 TLS 握手时发出的 SNI 值就是你要的那个域名:
- 在上游微服务所在服务器(比如另一台 Nginx)开启 debug 日志:
error_log /var/log/nginx/error.log debug; - 触发一次请求,搜索日志中
client sent server name行,看值是否匹配客户端 Host - 更直接:在 Nginx 本机执行
openssl s_client -connect 10.0.0.10:443 -servername api.tenant-a.com 2>/dev/null | openssl x509 -noout -text | grep "Subject:”,确认返回的证书 Subject CN 或 SAN 匹配该域名 - 注意:如果上游是 Java 微服务(如 Spring Boot + Netty),需确保其 TLS 层支持 SNI(JDK 8u251+ 默认启用)
最容易被忽略的是:SNI 是 TLS 层行为,和 HTTP 的 Host 头无关,但又必须靠它驱动;proxy_ssl_server_name 和 proxy_ssl_name 必须同时存在且作用域一致,少一个就白配。微服务调用链越深,中间代理层越多,漏掉这一环的概率越大。











