Nginx实现基于SNI的动态反向代理需分场景:stream模块可原生捕获$ssl_preread_server_name并配合map与proxy_ssl_name精准分发;HTTP层则依赖$host等变量“模拟”,须开启proxy_ssl_server_name on且不适用于严格SNI校验场景。

要实现基于 SNI 的动态反向代理分发,proxy_ssl_name 本身不能直接“动态”,但它可以和 map 指令配合,在 upstream 阶段将客户端 TLS 握手时发送的 SNI 值映射为后端所需的 SSL 主机名(即 SNI),从而让 Nginx 在与上游建立 TLS 连接时使用正确的 server_name。
理解 proxy_ssl_name 的作用
proxy_ssl_name 控制 Nginx 作为 TLS 客户端(连接 upstream 时)在 ClientHello 中发送的 SNI 扩展值。它默认是静态字符串,但可引用变量 —— 关键就在于这个变量能否动态取自原始请求的 SNI。
Nginx 本身不直接暴露原始 SNI 到 HTTP 上下文(因为 SNI 发生在 TCP/TLS 层,早于 HTTP 解析),但可通过 stream 模块预处理 + 变量透传,或更常用的是:利用 $host、$http_host 或自定义 header 间接推断(适用于 HTTPS 入口已终止并重写 Host 的场景)。不过,若需严格按原始 SNI 分发(如泛域名多租户、SNI 直通),必须用 stream + map + proxy_ssl_name 组合。
核心方案:stream 模块中捕获 SNI 并映射
这是真正实现“原始 SNI 动态分发”的标准做法,适用于 Nginx 1.19.4+(支持 ssl_preread_server_name 变量):
FastAPI + Flask 混合部署最佳实践,解决路由定义、API 代理等常见问题,适用于同时运行 FastAPI API 与 Flask 前端的场景。
- 启用
stream块,在 TLS 握手初期(未解密)读取 SNI:ssl_preread on; - 用
map将$ssl_preread_server_name映射为目标 upstream 名或完整后端地址:map $ssl_preread_server_name $upstream_backend {<br> default backend_default;<br> app1.example.com backend_app1;<br> api2.example.net backend_api2;<br> } - 在
stream server中使用该变量做代理,并设置proxy_ssl_name为原始 SNI(或映射后的目标名):proxy_pass $upstream_backend;<br> proxy_ssl_name $ssl_preread_server_name; # 或 $upstream_target_name<br> proxy_ssl on;
⚠️ 注意:proxy_ssl_name 在 stream 上下文中才真正起效;HTTP 上下文中的 proxy_ssl_name 仅影响到 upstream 的 TLS ClientHello,但无法获取原始 SNI(除非通过 header 注入)。
HTTP 层“模拟”动态 SNI(适用终止 HTTPS 后转发)
如果前端已用 Nginx 终止 HTTPS(即 client → Nginx 是 HTTPS,Nginx → upstream 也是 HTTPS),且你信任 Host 头或自定义头,则可在 HTTP 块中用 map + proxy_ssl_name:
- 在
http块定义映射:map $http_host $backend_sni {<br> ~^(?<domain>[^.]+)\.example\.com$ $domain.example.com;<br> default example.com;<br> } - 在
location中启用 upstream TLS 并指定 SNI:proxy_pass https://backend_pool;<br> proxy_ssl_name $backend_sni;<br> proxy_ssl_server_name on; # 必须开启,否则 proxy_ssl_name 不生效
✅ 此方式简单,但依赖 Host 头准确性(可被篡改),不适用于需要严格 SNI 匹配的场景(如证书验证强绑定)。
关键配置要点与常见陷阱
-
proxy_ssl_server_name on必须显式开启,否则proxy_ssl_name被忽略 -
proxy_ssl_name值必须是合法域名(不含端口),且应与 upstream 证书的 SAN 或 CN 匹配 -
stream模块的map不支持正则捕获组赋值给变量(Nginx ≤ 1.21),推荐用if+set替代复杂逻辑,或升级到 1.23+ - 若 upstream 使用自签名或私有 CA 证书,需配置
proxy_ssl_trusted_certificate和proxy_ssl_verify off(仅测试环境) - 调试建议:开启
error_log /path/to/log debug;并搜索"SSL_do_handshake" "SNI"确认实际发送的 SNI 值
不复杂但容易忽略:SNI 动态性本质依赖变量是否在 TLS 握手前就绪。stream ssl_preread 是唯一原生支持,HTTP 层只是妥协方案。选哪种,取决于你的架构是否允许 TLS 直通或必须终止。

















