NGINX SSL/TLS 预读模块解密 ngx_stream_ssl_preread_module 实战指南

发布于:2025-06-14 ⋅ 阅读:(22) ⋅ 点赞:(0)

一、模块概览

  • 模块名称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)入口,就根据客户端想访问的域名或协议,进行零拷贝无感知的路由分流

二、核心指令与变量

指令

ssl_preread on | off;
  • Contextstreamstream { 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 分流

# 将域名映射到不同上游
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 协议分流

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. 基于协议版本路由

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 版本降级兼容。

四、典型应用场景

  1. 二合一入口
    同端口同时做 SSH、HTTPS、gRPC、MQTT 等多协议采用零拷贝分流。
  2. 金丝雀/灰度路由
    根据客户端 SNI 域名,将小部分测试流量指向预发布环境。
  3. 智能协议网关
    前置读取 ALPN、协议版本后,路由至不同类型的业务集群。
  4. DDoS 与安全防护
    在 ClientHello 阶段就可基于 SNI 黑名单、弱协议版本(如 TLSv1.0)等条件做丢弃或限速,降低后端资源消耗。
  5. 运维监控统计
    利用 $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,快速在你的流量入口实现智 能路由吧!


网站公告

今日签到

点亮在社区的每一天
去签到