Nginx 的 stream_ssl_preread 模块支持仅读取 TLS 握手中的 SNI 字段实现四层域名分流,无需终止加密;需编译启用、配置 stream 块与 map 映射,并注意客户端 SNI 兼容性及后端证书匹配。

利用 Nginx 的 stream_ssl_preread 模块,可以在不终止(即不卸载)TLS 连接的前提下,仅通过读取 TLS 握手阶段的 SNI(Server Name Indication)字段,将流量按域名分发到不同后端。这适用于需要透传加密流量、又需基于域名做四层路由的场景,比如多个 HTTPS 服务共用一个 IP 和端口、或与 TLS 终止设备(如 WAF、LB)配合使用。
确认 Nginx 编译时启用了 stream_ssl_preread 模块
该模块默认不启用,需在编译时添加 --with-stream_ssl_preread_module。可通过以下命令验证:
nginx -V 2>&1 | grep -o with-stream-ssl-preread-module
若无输出,说明未启用,需重新编译或换用已包含该模块的发行版(如官方预编译包、Ubuntu 20.04+ 的 nginx-full 包等)。
配置 stream 块解析 SNI 并做条件分流
在 nginx.conf 的顶层(不在 http 块内)添加 stream 块,启用 ssl_preread on,再用 map 将 $ssl_preread_server_name 映射为后端地址:
示例配置:
stream {
# 启用 SNI 预读
ssl_preread on;
<pre class="brush:php;toolbar:false;"># 根据 SNI 域名映射后端
map $ssl_preread_server_name $backend {
hostnames;
example.com 192.168.1.10:443;
api.example.com 192.168.1.11:443;
default 192.168.1.12:443;
}
upstream backend_a {
server 192.168.1.10:443;
}
upstream backend_b {
server 192.168.1.11:443;
}
upstream backend_default {
server 192.168.1.12:443;
}
server {
listen 443;
proxy_pass $backend;
proxy_timeout 10s;
proxy_responses 1;
}}
- 关键点:SNI 在 ClientHello 中明文传输,无需解密 TLS 即可获取;Nginx 仅做转发,证书仍由后端真实服务提供。
-
注意:客户端必须支持并发送 SNI(现代浏览器和 curl 默认开启),否则
$ssl_preread_server_name为空,会走default分支。 - 限制:无法识别 ALPN、无法区分路径或 HTTP 头,仅限域名维度分流;不适用于无 SNI 的旧客户端(如 IE on WinXP)。
配合 TLS 透传与健康检查提升可用性
为保障转发稳定,建议补充基础健控和连接管理:
- 在
server块中设置proxy_timeout和proxy_responses 1,避免空连接堆积; - 对每个 upstream 添加
zone和health_check(需启用stream_upstream_zone和健康检查模块); - 若后端是 Kubernetes Service 或动态节点,可结合
resolver实现 DNS 轮询(注意:stream 不支持resolve指令,需用变量 + resolver + map 动态解析,较复杂,一般建议静态 IP 或用 consul-template 等外部工具生成配置)。
验证与排错要点
常见问题及验证方式:
-
SNI 未生效:用
openssl s_client -connect your-ip:443 -servername example.com测试,观察是否连到预期后端;同时检查 Nginx error log 是否有ssl preread: no SNI类提示。 -
连接被重置:确认后端监听 443 且允许透传(如 Nginx 后端需配
ssl_reject_handshake off或用ssl_passphrase等兼容模式);避免后端因证书域名不匹配主动断连(这是正常行为,不影响分流逻辑)。 -
map 不生效:确保
map声明在stream块最外层,且hostnames指令存在,支持通配符如*.example.com。


















