nginx stream模块通过ssl_preread机制提取sni/alpn实现四层动态路由,依赖map指令映射变量,支持协议无关、零延迟透传;关键变量仅$ssl_preread_server_name等少数可用,http变量不可用。

Nginx Stream 模块本身不支持应用层内容解析,但可通过预读机制(如 ssl_preread)提取 TLS 握手信息,并结合 map 指令实现基于 SNI 或 ALPN 的动态四层路由——这不是“解密”,而是对加密流量首包的轻量解析,真正做到了协议无关、零延迟、高并发。
变量来源有限,但关键字段可用
Stream 模块暴露的变量极少,真正可用于动态路由的只有几个:
-
$remote_addr和$remote_port:客户端 IP 和端口,适合 IP 哈希或黑白名单 -
$ssl_preread_server_name:需开启ssl_preread on,从 ClientHello 提取 SNI 域名(HTTPS/gRPC/TLS 服务必备) -
$ssl_preread_alpn_protocols:ALPN 协议列表,用于区分 HTTP/2、h2、grpc 等 -
$time_iso8601、$hostname等只读变量,一般不用于路由决策
注意:$host、$request_uri、$http_* 等 HTTP 变量在 stream 中完全不可用——它们属于 http 模块范畴。
用 map 构建动态 upstream 映射
Stream 不支持 if 或 rewrite,必须用 map 预先定义变量映射关系。例如,将不同 SNI 域名分发到不同后端集群:
# 在 stream 块外、events 同级定义(全局生效)
map $ssl_preread_server_name $upstream_backend {
default backend_default;
"api.example.com" backend_api;
"grpc.example.com" backend_grpc;
"~^v[0-9]+\.admin\.example\.com" backend_admin;
}
stream {
upstream backend_api {
server 10.0.1.10:443;
server 10.0.1.11:443;
}
upstream backend_grpc {
server 10.0.2.20:443;
server 10.0.2.21:443;
}
upstream backend_admin {
server 10.0.3.30:443;
}
upstream backend_default {
server 10.0.0.100:443;
}
server {
listen 443 ssl;
ssl_preread on; # 必须启用,否则 $ssl_preread_server_name 为空
proxy_pass $upstream_backend;
proxy_timeout 1h;
}
}
这个配置实现了单 IP + 单端口承载多个 TLS 服务,且无需终止证书——流量全程透传,Nginx 只做“智能分流”。
SNI 路由的硬性前提与常见陷阱
-
listen 443 ssl必须带ssl参数,否则ssl_preread不生效 - 客户端必须发送 SNI 扩展(现代浏览器、curl ≥7.60、gRPC 默认开启),老客户端或 IP 直连会走
default分支 -
map块不能放在stream { }内部,必须位于顶层(与events、http同级) - 正则匹配以
~开头,区分大小写;~*表示忽略大小写,适用于域名场景 - 若 SNI 为空(如某些 IoT 设备或自定义 TCP 客户端),变量值为
"",需在 map 中显式处理:"" backend_legacy;
扩展:用 ALPN 实现协议感知路由
当多个服务共用同一域名和端口(如 gRPC 与 HTTPS 同站部署),可依赖 ALPN 区分:
map $ssl_preread_alpn_protocols $alpn_backend {
~\bh2\b backend_h2;
~\bgrpc-exp\b backend_grpc;
~\bhttp\/1\.1\b backend_http1;
default backend_fallback;
}
配合 ssl_preread on,Nginx 可在 TLS 握手阶段识别协议意图,再决定转发目标——这对微服务网关、多协议 API 统一入口非常实用。
不复杂但容易忽略











