stream_preread模块是nginx在stream上下文中提取tls clienthello中sni字段实现四层域名分流的机制,无需ssl终止或解密,适用于tls透传网关;需nginx≥1.11.5并启用--with-stream-preread-module编译选项,通过$ssl_preread_server_name变量直接获取sni,配合if指令路由至不同upstream,注意无sni时需配置fallback。

Stream_preread 模块是 Nginx 在 stream(四层)上下文中提供的一个预读机制,可用于在建立完整 TLS 握手前,从客户端 ClientHello 中提取 SNI(Server Name Indication)字段,从而实现基于域名的 TCP/UDP 流量分流。它不依赖 SSL 终止,也不需要解密流量,适合用作 TLS 透传网关或前置路由层。
前提条件与编译支持
Nginx 必须启用 stream_preread 模块(默认不开启),且版本需 ≥ 1.11.5。检查方式:
nginx -V 2>&1 | grep -o with-stream-preread-module
若未出现,需重新编译 Nginx,添加:
--with-stream --with-stream-preread-module
注意:该模块仅作用于 stream { ... } 块内,不能用于 http 上下文。
提取 SNI 并设置变量
在 stream 块中使用 preread_buffer_size 和 preread 指令,配合正则提取 SNI:
- preread_buffer_size 至少设为 2048,确保能覆盖典型 ClientHello(含扩展字段)
- preread 启用预读,并用 preread_variables_hash_max_size 和 preread_variables_hash_bucket_size 配合正则捕获
- SNI 位于 ClientHello 的扩展段(type=0),需用 PCRE 正则匹配二进制结构,Nginx 提供内置变量 $ssl_preread_server_name —— 这是最简方式,无需手动写正则
✅ 推荐做法(Nginx ≥ 1.11.6):
stream {
upstream backend_a { server 192.168.1.10:443; }
upstream backend_b { server 192.168.1.11:443; }
server {
listen 443;
preread on;
# 自动解析 SNI 到 $ssl_preread_server_name
set $backend upstream_default;
if ($ssl_preread_server_name ~* ^app\.example\.com$) {
set $backend backend_a;
}
if ($ssl_preread_server_name ~* ^api\.example\.com$) {
set $backend backend_b;
}
proxy_pass $backend;
}
}
注意事项与常见限制
- $ssl_preread_server_name 只在 TLS ClientHello 中存在 SNI 时才有效;纯 IP 访问、旧客户端(无 SNI)或恶意构造包会导致该变量为空,需设置 fallback(如 default upstream)
- 不支持 ALPN、证书验证、会话复用等 TLS 层逻辑 —— 它只是“看一眼就转发”,后续 TLS 握手由后端真实服务完成
- 无法识别 HTTP Host 头(那是七层),也不能区分同一域名下的不同路径
- 若后端要求 SNI 一致性(如某些 gRPC/TLS 服务),需确保 proxy_pass 不改写 SNI —— Nginx stream 默认透传,无需额外配置
验证是否生效
用 openssl 模拟带 SNI 的连接,观察日志或后端接收情况:
openssl s_client -connect your-nginx-ip:443 -servername app.example.com -quiet
同时开启 Nginx error_log debug 级别,搜索 "ssl preread" 关键字,可确认是否成功提取。
也可在 server 块中加入 log_format + access_log,记录 $ssl_preread_server_name 值用于排查。











