nginx通过stream模块+ssl_preread子模块在tcp层解析sni实现四层分发,需启用--with-stream、--with-stream_ssl_module、--with-stream_ssl_preread_module三模块,并在stream{}块中用map基于$ssl_preread_server_name动态proxy_pass。

要让 Nginx 通过 ssl_preread 实现 SNI 分发,核心不是加载某个“模块开关”,而是启用 stream 模块 + ssl_preread 子模块,并在四层(TCP)上下文中解析 TLS 握手阶段的原始 SNI 字段。HTTP 模块完全无法触达这个信息,所有基于 proxy_ssl_name 或 Host 头的配置在这里都无效。
确认模块已编译并启用
运行以下命令验证关键模块是否存在:
-
nginx -V 2>&1 | grep -o with-stream—— 必须有输出 -
nginx -V 2>&1 | grep -o with-stream_ssl_preread_module—— 必须有输出 -
nginx -V 2>&1 | grep -o with-stream_ssl_module—— 必须有输出(三者缺一不可)
若缺失,需重新编译 Nginx,参数必须包含:--with-stream --with-stream_ssl_module --with-stream_ssl_preread_module
配置必须放在 stream {} 块中
所有相关指令只能写在顶层 stream { } 块内,不能嵌套在 http 或其他上下文中。典型结构如下:
- 用
map $ssl_preread_server_name $backend做域名到后端的映射,支持hostnames指令和通配符(如*.api.example.com) - 每个目标后端定义为
upstream块,地址格式为IP:PORT(如10.0.0.5:443) -
server块监听 TLS 端口(通常是 443),且必须显式写ssl_preread on -
proxy_pass引用 map 输出的变量,实现动态转发
处理常见边界情况
SNI 并非总存在,实际部署中需兼顾兼容性:
- 旧客户端或某些 CLI 工具可能不发送 SNI,
$ssl_preread_server_name为空,map中的default条目必须设置 - 非 TLS 流量(如直连 HTTP、MySQL 明文协议)会绕过
ssl_preread,建议单独监听非 TLS 端口或加防火墙隔离 - ALPN、协议版本等字段也可用于分流,例如用
$ssl_preread_alpn_protocols区分 HTTP/2 和 HTTP/1.1
一个可直接运行的最小配置示例
stream {map $ssl_preread_server_name $backend {
hostnames;
app.example.com backend_app;
api.example.com backend_api;
default backend_default;
}
upstream backend_app { server 192.168.10.10:443; }
upstream backend_api { server 192.168.10.11:443; }
upstream backend_default { server 192.168.10.12:443; }
server {
listen 443;
ssl_preread on;
proxy_pass $backend;
proxy_timeout 1s;
proxy_responses 1;
}
}











