Nginx四层负载均衡默认不解析HTTP,但通过启用ssl_preread模块可读取SNI字段实现按域名分流,并结合SSL卸载将TLS流量路由至不同后端TCP服务。

Nginx 四层负载均衡本身不解析 HTTP 协议,因此默认无法根据域名(SNI)做路由。但通过 ssl_preread 模块,可以在 TLS 握手初期读取客户端发送的 SNI(Server Name Indication)字段,从而在四层阶段实现“按域名分流”,再结合 SSL 卸载(即在 Nginx 终止 TLS),就能兼顾性能与灵活性。
这个组合适用于多租户数据库网关、混合协议网关(如多个 MySQL 实例共用 443 端口)、或需将不同域名的 TLS 流量导向不同后端 TCP 服务(如不同客户的 Redis/PostgreSQL 实例)等场景。
前提:确认 ssl_preread 模块已启用
该模块从 Nginx 1.11.5 起内置,但编译时需显式开启:
nginx -V 2>&1 | grep -o with-stream nginx -V 2>&1 | grep -o with-ssl-preread
若无 with-ssl-preread,需重新编译:
./configure --with-stream --with-stream_ssl_preread_module --with-stream_ssl_module ...
注意:--with-stream_ssl_preread_module 和 --with-stream_ssl_module 必须同时启用,缺一则无法读取 SNI 或建立 TLS 代理。
核心配置:基于 SNI 的四层路由 + SSL 卸载
stream 块中不能直接写 ssl_certificate(那是 http 块的功能),SSL 卸载在此处体现为:Nginx 终止客户端 TLS 连接,并以明文 TCP 连接转发至后端。关键靠 proxy_ssl 系列指令配合 ssl_preread on 实现识别与转发。
示例:同一 443 端口,根据 SNI 将流量分发到两个不同 MySQL 实例
stream {
# 启用 SNI 解析(必须放在 server 块内或 upstream 上方)
map $ssl_preread_server_name $backend_mysql {
hostnames;
db1.example.com mysql-db1;
db2.example.com mysql-db2;
default mysql-db1; # 默认兜底
}
upstream mysql-db1 {
server 192.168.10.11:3306;
}
upstream mysql-db2 {
server 192.168.10.12:3306;
}
server {
listen 443 ssl;
ssl_preread on; # 关键:开启 SNI 预读
# SSL 卸载所需证书(用于和客户端完成 TLS 握手)
ssl_certificate /etc/nginx/ssl/wildcard.example.com.pem;
ssl_certificate_key /etc/nginx/ssl/wildcard.example.com.key;
# 转发到对应 upstream
proxy_pass $backend_mysql;
# 可选:连接超时控制
proxy_timeout 1h;
proxy_responses 1;
}
}⚠️ 注意事项:
-
listen 443 ssl表示该server接收 TLS 流量(不是普通 TCP); -
ssl_preread on必须开启,否则$ssl_preread_server_name为空; -
ssl_certificate和ssl_certificate_key是必需的——Nginx 需用它们响应 ClientHello,完成 TLS 握手(即 SSL 卸载); - 后端(如 MySQL)收到的是明文 TCP 连接,无需支持 TLS(除非你额外启用
proxy_ssl on做二次加密,那就不算卸载了); -
map块必须定义在stream { }内,且在所有upstream之前(语法顺序敏感); -
hostnames参数允许使用通配符域名(如*.example.com),但仅匹配一级子域。
验证与调试方法
-
使用 OpenSSL 检查 SNI 是否被正确识别:
openssl s_client -connect your.nginx.ip:443 -servername db1.example.com -quiet 2>/dev/null | head -5
若返回 MySQL 的初始握手包(如
0a382e30...开头的二进制流),说明 SNI 匹配成功且已透传。 -
查看 Nginx 错误日志:
tail -f /var/log/nginx/error.log
常见报错:
-
no "ssl_certificate" is defined→ 忘加证书路径; -
the "ssl_preread" directive must be enabled→ssl_preread on缺失; -
unknown variable "$ssl_preread_server_name"→map块位置错误或未加载ssl_preread模块。
-
使用
nginx -t检查语法,确保stream块与http块同级、且位于events之后。
进阶:多协议共用 443 端口(HTTP + MySQL + 自定义 TCP)
可通过 ssl_preread_server_name + 多层 map 实现协议识别:
map $ssl_preread_server_name $proto_backend {
~^api\. http-backend;
~^db\. mysql-backend;
~^redis\. redis-backend;
default http-backend;
}
map $proto_backend $upstream_name {
http-backend http_upstream;
mysql-backend mysql_upstream;
redis-backend redis_upstream;
}再配合不同 upstream 定义和 proxy_pass $upstream_name,即可在一个端口承载多种 TLS 服务。
这种架构常见于边缘网关、SaaS 平台统一接入层,既节省端口资源,又保持四层性能优势。
不复杂但容易忽略。


















