一、模块概览
-
模块名称 :
ngx_stream_ssl_preread_module
-
首次引入 :NGINX 1.11.5(2016-05-03)
-
编译选项 :
--with-stream_ssl_preread_module
(需启用--with-stream
) -
核心功能 :在不解密SSL/TLS连接的前置读(preread)阶段,直接从 ClientHello 中提取:
- SNI(Server Name Indication)
- ALPN(Application-Layer Protocol Negotiation)
- 最高支持协议版本
这一能力让你能在四层(TCP/UDP)入口,就根据客户端想访问的域名或协议,进行零拷贝 、无感知的路由分流。
二、核心指令与变量
指令
nginx
ssl_preread on | off;
- Context :
stream
、stream { server { ... } }
- 默认 :
off
- 功能:开启后,NGINX 在收到 TCP 握手数据(ClientHello)后、正式解密前,提取 SNI、ALPN、协议版本等信息。
内置变量
变量名 | 含义 | 引入版本 |
---|---|---|
$ssl_preread_server_name |
从 ClientHello 的 SNI 扩展中抓取到的服务器域名(或空串) | 1.11.5 |
$ssl_preread_alpn_protocols |
客户端在 ALPN 扩展中声明的、以逗号分隔的协议列表(如 h2,http/1.1 ) |
1.13.10 |
$ssl_preread_protocol |
客户端支持的最高 TLS/SSL 版本,如 TLSv1.3 ;空串表示无 SSL 握手信息 |
1.15.2 |
三、配置示例
1. 基于 SNI 分流
nginx
# 将域名映射到不同上游
map $ssl_preread_server_name $backend {
backend.example.com backend1;
api.example.com backend2;
default backend_default;
}
upstream backend1 { server 10.0.0.1:443; }
upstream backend2 { server 10.0.0.2:443; }
upstream backend_default { server 10.0.0.3:443; }
stream {
server {
listen 443;
ssl_preread on;
proxy_pass $backend;
}
}
- 流程 :客户端发起 TLS 握手 ➔ NGINX 读取 SNI ➔ 根据
$backend
动态路由 ➔ 再交由proxy_pass
进行底层转发。
2. 基于 ALPN 协议分流
nginx
map $ssl_preread_alpn_protocols $backend {
~\bh2\b http2_upstream;
~\bhttp/1.1\b http1_upstream;
default generic_upstream;
}
upstream http2_upstream { server 10.0.1.1:8443; }
upstream http1_upstream { server 10.0.1.2:8080; }
upstream generic_upstream { server 10.0.1.3:9000; }
stream {
server {
listen 443;
ssl_preread on;
proxy_pass $backend;
}
}
- 示例场景 :同端口承载 gRPC(
h2
)、传统 HTTPS(http/1.1
)与其他 TLS 应用。
3. 基于协议版本路由
nginx
map $ssl_preread_protocol $backend {
"" ssh_upstream; # 非 TLS 客户端,如 SSH 直接访问
"TLSv1.2" tls12_upstream;
default tls_upstream;
}
upstream ssh_upstream { server 10.0.2.1:22; }
upstream tls12_upstream { server 10.0.2.2:443; }
upstream tls_upstream { server 10.0.2.3:443; }
stream {
server {
listen 443;
ssl_preread on;
proxy_pass $backend;
}
}
- 应用示例:在同一端口同时支持 SSH 与 HTTPS,或根据客户端 TLS 版本降级兼容。
四、典型应用场景
- 二合一入口
同端口同时做 SSH、HTTPS、gRPC、MQTT 等多协议采用零拷贝分流。 - 金丝雀/灰度路由
根据客户端 SNI 域名,将小部分测试流量指向预发布环境。 - 智能协议网关
前置读取 ALPN、协议版本后,路由至不同类型的业务集群。 - DDoS 与安全防护
在 ClientHello 阶段就可基于 SNI 黑名单、弱协议版本(如 TLSv1.0)等条件做丢弃或限速,降低后端资源消耗。 - 运维监控统计
利用$ssl_preread_*
变量埋点 NGINX 日志,统计不同域名、协议类型、TLS 版本的连接分布。
五、性能与注意事项
-
零解密开销:只读取前置数据,不做完整握手或证书校验,CPU 开销极低。
-
必须先开启
ssl_preread on;
:否则$ssl_preread_*
变量为空,路由失效。 -
变量映射效率高 :
map
内部使用哈希查找,百万 QPS 场景也能稳定分流。 -
与
ngx_stream_ssl_module
区别:ssl_preread
仅读取 ClientHello,不终结 TLS;ssl
指令(ngx_stream_ssl_module
)则终结 TLS 握手并解密后续流量。
-
安全性考量:预读阶段不能校验客户端证书或执行 OCSP,可配合后置 TLS 终结或接入防火墙。
-
SNI 空白 :部分老旧客户端不发送 SNI,会走
default
分支,需合理配置兜底上游。
六、小结
ngx_stream_ssl_preread_module
给 NGINX 四层代理带来了"先看再转"的魔法:在不解密的前提下,提取 SNI、ALPN 与版本信息,实现灵活、零拷贝的多协议分流与灰度路由。无论是合并 SSH/HTTPS、按域名做灰度,还是搭建智能协议网关,这个模块都能助你一臂之力。
动手即见效 :升级到 NGINX ≥1.11.5,启用 --with-stream_ssl_preread_module
,快速在你的流量入口实现智 能路由吧!