要让nginx真正透传客户端sni,必须启用stream模块和ssl_preread模块,在四层解析client hello中的sni字段,通过map映射路由至不同后端;http模块无法获取原始sni,proxy_ssl_name等七层配置无效。

要让 Nginx 反向代理真正透传客户端的 SNI(Server Name Indication),不是“转发 Host 头”那么简单——SNI 是 TLS 握手阶段的原始字段,发生在 HTTP 层之前,必须在 TCP 连接建立初期就捕获并用于上游路由。Nginx 默认不处理这一层,需启用 stream 模块 和 ssl_preread 模块,走纯四层透传路径。
必须启用 stream 上下文和 ssl_preread 支持
Nginx 的 http 模块无法读取 Client Hello 中的 SNI,只有 stream 模块配合 ssl_preread 才能提前解析。编译时需确认已启用:
--with-stream--with-stream-ssl-module--with-stream-ssl-preread-module
若用包管理安装(如 apt/yum),需安装带 stream 支持的版本(例如 Ubuntu 的 nginx-full 或 CentOS 的 nginx-mod-http-stream)。
配置 ssl_preread 提取 SNI 并做路由映射
在 stream { } 块中启用预读,并用 map 将 SNI 值映射到后端地址:
stream {
map $ssl_preread_server_name $upstream {
hostnames;
~^(?<domain>.+?)\.example\.com$ $domain-backend:443;
~^api\.(.+)$ api-cluster:443;
default fallback:443;
}
upstream api-cluster {
server 10.0.1.10:443;
server 10.0.1.11:443;
}
server {
listen 443 ssl;
ssl_preread on;
proxy_pass $upstream;
proxy_ssl on;
proxy_timeout 1s;
}
}</domain>
关键点:
-
ssl_preread on;是触发 SNI 解析的开关 -
map必须声明hostnames,支持通配符和正则匹配 -
$ssl_preread_server_name是唯一可获取原始 SNI 的变量,不可用$host替代 -
proxy_ssl on;启用上游 TLS 透传(不终止、不解密)
避免常见陷阱
很多配置看似合理,实则无效:
- 在
http { }块里设proxy_ssl_name $host—— 这只是告诉上游“我代表谁访问”,不是透传客户端原始 SNI - 只开
proxy_ssl_server_name on而不用stream—— 仍属七层代理,SNI 已丢失 - 未校验上游证书或忽略
proxy_ssl_verify off(调试时需关,生产环境建议保留验证) - 监听 443 但未绑定 SSL 证书(
ssl_certificate可为任意有效证书,仅用于接受 TLS 握手,不参与解密)
验证 SNI 是否真实透传
最可靠方式是抓包或查上游日志:
- 在上游服务器(如另一台 Nginx 或 OpenSSL s_server)开启 debug 日志:
error_log /var/log/nginx/error.log debug;,搜索SSL_do_handshake或sni相关行 - 用
openssl s_client -connect your-proxy:443 -servername test.example.com测试,观察上游是否返回对应域名的证书 - Wireshark 抓包,过滤
tls.handshake.type == 1(Client Hello),直接查看 Server Name 字段值











