Nginx ssl_preread模块可在不终止TLS连接时提取ClientHello中的SNI字段,实现基于域名的四层转发;需启用--with-stream_ssl_preread_module编译选项,配置中通过ssl_preread on和$ssl_preread_server_name变量结合map指令完成SNI路由。

nginx 的 ssl_preread 模块可以在不终止 TLS 连接的前提下,提取客户端 ClientHello 中的 SNI(Server Name Indication)字段,从而实现基于域名的 TCP 层转发。这在反向代理 HTTPS、SMTP、IMAP 等 TLS 协议服务时非常实用,避免了证书透传或 TLS 终止带来的复杂性。
确认 nginx 已启用 ssl_preread 模块
该模块自 nginx 1.11.5 起默认编译进核心,但部分发行版(如 Ubuntu 官方包)可能未启用。可通过以下命令验证:
nginx -V 2>&1 | grep -o with-stream_ssl_preread_module
若无输出,需重新编译 nginx 并添加 --with-stream_ssl_preread_module 参数;或改用官方 nginx.org 提供的预编译包。
配置 stream 上下文支持 SNI 提取与变量映射
必须在 stream 块中启用 ssl_preread on,并使用内置变量 $ssl_preread_server_name 获取 SNI 域名。注意:该变量仅在启用 ssl_preread 后可用,且只对 TLS 握手有效(非 TLS 流量该变量为空)。
示例配置片段:
stream {
upstream backend_a {
server 192.168.1.10:443;
}
upstream backend_b {
server 192.168.1.11:443;
}
map $ssl_preread_server_name $upstream_backend {
hostnames;
example.com backend_a;
api.example.com backend_b;
default backend_a;
}
server {
listen 443;
ssl_preread on;
proxy_pass $upstream_backend;
proxy_timeout 1s;
proxy_responses 1;
}
}
处理常见边界情况
实际部署中需注意以下几点:
- SNI 为空时(如旧客户端、非 TLS 流量),
$ssl_preread_server_name为空字符串,应通过map的default明确指定兜底上游 - 客户端未发送 SNI(TLS 1.2 及以下允许省略),则无法分流;TLS 1.3 强制要求 SNI,但兼容性需评估
-
proxy_timeout 1s和proxy_responses 1可加快失败判定,避免因握手异常导致连接长时间挂起 - 不支持通配符匹配(如
*.example.com),但map的hostnames指令支持以点开头的域名前缀匹配(如.example.com匹配foo.example.com和example.com)
验证与调试方法
可使用 openssl s_client 手动指定 SNI 发起测试:
openssl s_client -connect your-nginx-ip:443 -servername example.com -tls1_2
同时开启 nginx 错误日志(error_log /var/log/nginx/stream.log debug;),查看是否记录 ssl preread server name: 行。注意 debug 日志需重新编译时启用 --with-debug,生产环境慎用。











