根本原因是sni字段未正确传递:nginx默认用proxy_pass中的地址(如ip或内网域名)作为sni值,导致后端无法匹配证书或路由,引发502、证书错误或403;必须成对配置proxy_ssl_server_name on和proxy_ssl_name(动态用$host或静态写死),并确保与host头一致。

请求打到错误的 Server,根本原因不是 Nginx 转发错了,而是 TLS 握手阶段就已“失联”——后端因收不到正确的 SNI 值,无法选出匹配证书,被迫返回默认证书、拒绝连接,或由业务逻辑(如租户路由)误判为其他域名请求。浏览器看到证书不匹配,用户看到安全警告;Nginx 则常报 502 Bad Gateway、SSL certificate verify failed 或静默中断,看似“转发到了错误 server”,实则是握手失败导致上游无响应。
核心问题:SNI 字段没传对,不是没传
Nginx 默认会发 SNI,但发的是 proxy_pass 后写的地址(比如 https://10.0.0.100 或 https://backend.internal),而不是用户访问的 shop-a.com。当后端一个 IP 托管多个域名(如 shop-a.com、api.shop-b.net、bucket-c.storage.example.com),它只能靠 SNI 值来查证书或路由。若 SNI 是 IP 或固定内网名,后端就只能 fallback 到第一个证书或默认租户,结果就是:
- 用户访问
shop-a.com,后端返回shop-b.net的证书 → 浏览器报错 - 用户请求
bucket-c.storage.example.com,后端因 SNI 不匹配直接断连 → Nginx 报 502 - 微服务网关把所有无 SNI 或错 SNI 请求都路由给 “tenant-default” → 数据写错库
关键配置必须成对出现
proxy_ssl_server_name on 只是打开“发送 SNI”的开关,真正决定发什么的,是 proxy_ssl_name。二者缺一不可:
- 只开
proxy_ssl_server_name on,不设proxy_ssl_name→ 发默认值(proxy_pass中的主机名/IP),仍错配 - 只设
proxy_ssl_name $host,不开proxy_ssl_server_name on→ SNI 扩展根本不发,后端收不到任何域名信息 - 正确写法(放在
location块中):
proxy_ssl_server_name on;
proxy_ssl_name $host;
CDN 或网关导致 $host 失真,是高频陷阱
Cloudflare、阿里云全站加速、WAF、API 网关等中间层,回源时通常会重写 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;,方便后端日志和租户识别
upstream 场景下变量失效,必须写死
如果用了 upstream 块定义后端(例如负载多个对象存储网关),Nginx 在 SSL 握手阶段无法解析运行时变量。此时 $host 会报错或被忽略。
- 必须在
location中显式指定字符串:proxy_ssl_name "bucket-a.example.com";(带双引号) - 该字符串必须与后端证书的 SAN(Subject Alternative Name)完全一致,大小写敏感
- 泛域名证书(
*.storage.example.com)要填具体子域,如my-bucket.storage.example.com











