Nginx | stream 四层反向代理:SSL、PREREAD 阶段模块指令浅析与实践

知识是人生的灯塔,只有不断学习,才能照亮前行的道路

📢 大家好,我是 WeiyiGeek,一名深耕安全运维开发(SecOpsDev)领域的技术从业者,致力于探索DevOps与安全的融合(DevSecOps),自动化运维工具开发与实践,企业网络安全防护,欢迎各位道友一起学习交流、一起进步 🚀,若此文对你有帮助,一定记得点个关注⭐与小红星❤️或加入到作者知识星球『 全栈工程师修炼指南』,转发收藏学习不迷路 😋 。

SSL 阶段

描述:前文《Nginx | stream 四层反向代理:PREACCESS、ACCESS 阶段并发与访问限制模块浅析与实践》作者讲解了四层反向代理并发数限制以及与访问限制 ,由于互联网环境对安全性要求越来越高,要求中间件或应用在处理TCP协议时支持携带 TLS/SSL 的安全通信通讯,作为负载均衡的 Nginx 当然也是支持的,在四层反向代理中能够处理来自下游客户端带有 TLS/SSL 加密的连接,并透传裸TCP至上游服务器。

温馨提示:若文章代码块中存在乱码或不能复制,请联系作者,也可通过文末的阅读原文链接,加入知识星球中阅读,原文链接:https://articles.zsxq.com/id_t5z28zcqv7us.html

使用 TLS/SSL 的三种应用场景如下所示,其中安全与非安全通信标识,绿色线条表示使用TLS/SSL的安全网络通信(如HTTPS),蓝色线条表示未加密的裸TCP协议。

  • • 场景1:透传SSL流,Nginx 不解析SSL,直接将客户端发来的带有SSL协议的TCP流转发给上游服务器。

  • • 场景2:剥离SSL层(重点学习),Nginx 解析客户端发来的TLS协议,将其转换为裸TCP协议后再发送至上游服务。

  • • 场景3:后端加密,客户端与NGX之间无SSL协议,但NGX向上游服务发送请求时启用SSL/TLS。

weiyigeek.top-四层代理使用 `TLS/SSL` 的三种应用场景图

◆ ssl 模块

描述:ngx_stream_ssl_module 模块使stream反向代理支持对下游客户端的TLS/SSL协议处理,默认不编译进NGX,可通过 --with-stream_ssl_module 参数显式加入该模块。

指令参数

实际上 steam 中的 ssl 指令参数与 HTTP 模块中的 ssl 指令几乎完全相同,大家可参考 ngx_stream_ssl_module 模块文档了解,此处仅列出部分常用指令参数。

go 复制代码
stream {
  map $ssl_alpn_protocol$proxy {
    h2                127.0.0.1:8001;
    http/1.1          127.0.0.1:8002;
  }

# 全局配置,例如:全局私钥密码文件路径。
  ssl_password_file /etc/keys/global.pass;

  server {
    listen      12346 ssl;
    proxy_pass  $proxy;

#  ALPN(Application-Layer Protocol Negotiation)协议,例如:h2 和 http/1.1
    ssl_alpn    h2 http/1.1;

# 在 OpenSSL 1.0.2 及更高版本中支持不同的证书和密钥对,例如:RSA and ECDSA
    ssl_certificate     example.com.rsa.crt;
    ssl_certificate_key example.com.rsa.key;

    ssl_certificate     example.com.ecdsa.crt;
    ssl_certificate_key example.com.ecdsa.key;

# 当且仅当密钥文件被加密时,需要此参数。
    ssl_password_file global.pass;

# 支持的协议版本列表,例如:TLSv1.2 和 TLSv1.3
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3;

# 支持的加密套件列表
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE:ECDH:AES:HIGH:EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:!NULL:!aNULL:!eNULL:!EXPORT:!PSK:!ADH:!DH:!DES:!MD5:!RC4;
# 指定SSL握手超时时间,默认60秒。由于 http 模块中不关注握手超时,此处仅在 stream 模块中才有。
    ssl_handshake_timeout 10s;
  }
}

weiyigeek.top-stream ssl 常用指令图

  • • ssl_alpn :支持的ALPN协议列表。
go 复制代码
Syntax: ssl_alpn protocol ...;
Default: ---
Context: stream, server

# 例如,表示支持 HTTP/2 和 HTTP/1.1 协议。
ssl_alpn    h2 http/1.1;
  • • ssl_certificate: 指定服务器证书文件路径,用于SSL握手时验证服务端身份。
go 复制代码
Syntax: ssl_certificate file;
Default: ---
Context: stream, server
  • • ssl_certificate_key: 指定服务器私钥文件路径,用于SSL握手时验证服务端身份。
go 复制代码
Syntax: ssl_certificate_key file;
Default:---
Context: stream, server
  • • ssl_password_file: 指定包含私钥密码的文件路径,用于解密服务器私钥。例如,当使用加密的密钥文件时需要此参数。
go 复制代码
Syntax: ssl_password_file file;
Default: ---
Context: stream, server
  • • ssl_ciphers: 指定支持的加密套件列表,用于协商安全连接。
go 复制代码
Syntax: ssl_ciphers ciphers;
Default: ssl_ciphers HIGH:!aNULL:!MD5;
Context: stream, server

# 例如:表示使用高强度加密算法且不包含匿名和MD5算法。
HIGH:!aNULL:!MD5
  • • ssl_protocols: 指定支持的SSL协议版本列表,为了安全建议使用TLSv1.2 和 TLSv1.3 协议版本。
go 复制代码
Syntax: ssl_protocols [SSLv2] [SSLv3] [TLSv1] [TLSv1.1] [TLSv1.2] [TLSv1.3];
Default: ssl_protocols TLSv1.2 TLSv1.3;
Context: stream, server

# 特别注意:
TLSv1.1 and TLSv1.2 : 仅在使用OpenSSL 1.0.1或更高版本时有效。
TLSv1.3 : 仅在使用OpenSSL 1.1.1或更高版本时有效,从 1.23.4 版本开始为缺省值。
  • • ssl_handshake_timeout: 设置SSL握手超时时间,默认60秒,由于 http 模块中不关注握手超时,此处仅在 stream 模块中才有。
go 复制代码
Syntax: ssl_handshake_timeout time;
Default: ssl_handshake_timeout 60s;
Context: stream, server
  • • ssl_certificate_cache:用于缓存服务器证书,减少每次SSL握手时的磁盘I/O开销。默认关闭,可通过设置参数启用。
go 复制代码
Syntax: ssl_certificate_cache off;
ssl_certificate_cache max=N [inactive=time] [valid=time];
Default: ssl_certificate_cache off;
Context: stream, server

# 参数说明:
max:设置缓存中可存储的客户端证书数量。
inactive: 设置缓存项在不被访问后多久自动失效,例如 60s。
valid:设置缓存项的有效期,例如 10m 表示有效期为 10 分钟。

# 例如,表示最多存储 1000 个证书,如果超过60秒未被访问则自动失效,有效期为 10 分钟。
ssl_certificate_cache max=1000 inactive=60s valid=10m;
  • • ssl_session_cache: 设置SSL会话缓存,用于存储和复用之前的TLS握手信息。默认关闭,可通过设置参数启用。
go 复制代码
Syntax: ssl_session_cache off | none | [builtin[:size]] [shared:name:size];
Default: ssl_session_cache none;
Context: stream, server

# 参数说明:
off :禁用会话缓存。
none :测试模式,不实际缓存会话。
builtin[:size] :使用内置缓存,可选参数 size 指定最大内存大小,若如果没有指定大小,则等于20480个会话,另外使用内置缓存可能会导致内存碎片,所以通常不建议使用。
shared:name:size :使用共享区域缓存,其中 name 是缓存的名称,size 指定最大内存大小,1兆字节可以存储大约4000个会话。
  • • ssl_session_tickets: 启用或禁用TLS会话票证(Session Tickets)功能,默认开启。
go 复制代码
Syntax: ssl_session_tickets on | off;
Default: ssl_session_tickets on;
Context: stream, server
  • • ssl_session_ticket_key: 设置会话票据密钥,用于启用TLS会话票证(Session Tickets)功能。默认关闭,可通过设置参数启用,如果同一密钥必须在多个服务器之间共享,则该指令是必需的。
go 复制代码
Syntax: ssl_session_ticket_key file;
Default: ---
Context: stream, server

# 根据文件大小,可以使用AES256(用于80字节密钥,1.11.8)或AES128(用于48字节密钥)进行加密。
openssl rand 80 > ticket.key
ssl_session_ticket_key current.key;
  • • ssl_session_timeout: 设置SSL会话超时时间,默认5分钟。
go 复制代码
Syntax: ssl_session_timeout time;
Default: ssl_session_timeout 5m;
Context: stream, server
  • • ssl_verify_client: 启用或禁用客户端证书验证,默认关闭。
go 复制代码
Syntax: ssl_verify_client on | off | optional | optional_no_ca;
Default: ssl_verify_client off;
Context: stream, server
  • • ssl_verify_depth: 设置客户端证书链的验证深度,默认1, 从信任的根 CA 开始计数,不包含服务器证书本身,例如设置为2,则验证客户端证书链的前两个证书,但是值越大,验证可能越耗时
go 复制代码
Syntax: ssl_verify_depth number;
Default: ssl_verify_depth 1;
Context: stream, server
  • • ssl_trusted_certificate: 指定一个或多个可信CA证书文件,用于验证客户端证书或OCSP响应。默认不启用此功能。
go 复制代码
Syntax: ssl_trusted_certificate file;
Default:---
Context: stream, server

另外,Stream SSL 模块中还提供了一些特殊的变量,大致分为如下几类,例如

安全套件信息:

  • • $ssl_cipher:获取本次通信使用的确切安全套件

  • • $ssl_client_ciphers:列出客户端(如浏览器)支持的所有安全套件

  • • $ssl_protocol:显示当前使用的TLS协议版本(如 TLSv1.2)

  • • $ssl_alpn_protocol:显示客户端支持的ALPN协议,例如 h2 表示HTTP/2。

  • • $ssl_curves:显示协商使用的椭圆曲线(如 X25519, secp384r1)

  • • $ssl_ech_status: 显示客户端支持的加密扩展协议状态,例如 "FAILED", "BACKEND", "GREASE", "SUCCESS", or "NOT_TRIED" (1.29.4);

证书内容信息:

  • • $ssl_client_raw_cert:返回原始客户端证书内容。

  • • $ssl_client_escaped_cert:返回经URL编码后的客户端证书。

  • • $ssl_client_cert:返回客户端证书的PEM格式内容。

  • • $ssl_client_fingerprint:返回客户端证书的SHA-1指纹。

证书结构化信息:特别注意 stream 模块不支持旧版(legacy)以反斜杠分隔的DN格式,仅支持标准格式。

  • • $ssl_server_name:通过SNI扩展获取客户端请求的域名。

  • • $ssl_client_i_dn:获取客户端证书的Issuer DN信息(如国家、组织等)。

  • • $ssl_client_s_dn: 获取客户端证书的Subject DN信息,每行前加Tab符,便于打印输出。

证书有效期信息:

  • • $ssl_client_v_end:返回客户端证书的过期时间(格式示例:May 17 09:35:15 2033 GMT)。

  • • $ssl_client_v_remain:返回客户端证书剩余有效天数(单位:天),例如值为 3649 表示约十年。

  • • $ssl_client_v_start:返回客户端证书由CA颁发的起始日期。

连接有效性相关变量:

  • • $ssl_client_serial_no:返回客户端证书的序列号(十六进制格式)。

  • • $ssl_client_verify:返回客户端证书验证结果:失败时显示具体原因,未验证返回空字符串,成功返回 "SUCCESS"。

  • • $ssl_session_id:获取已建立连接的SSL会话ID,在OpenResty中常用。

  • • $ssl_session_reused:指示会话是否被复用,是则返回 "r",否则为 "."。

实践示例

目的:展示 Nginx 作为四层反向代理时将客户端带SSL的TCP流剥离SSL层后,以裸TCP形式转发至上游服务器。特别注意,虽使用HTTP协议进行演示,但 Nginx 的stream模块仅处理TCP与SSL层,不关心应用层协议。

步骤 01.重新构建编译 Nginx 以启用 stream ssl 模块。

go 复制代码
# 构建
./configure --prefix=/usr/local/nginx --user=nginx --group=nginx --sbin-path=/usr/sbin/nginx --conf-path=/usr/local/nginx/conf/nginx.conf --pid-path=/usr/local/nginx/nginx.pid --error-log-path=/var/log/nginx/logs/error.log --http-log-path=/var/log/nginx/logs/access.log --lock-path=/var/run/nginx.lock --modules-path=/usr/local/nginx/modules --with-http_stub_status_module --with-http_realip_module --with-http_v2_module --with-http_ssl_module --with-http_slice_module --with-stream --with-stream_realip_module  --with-stream_ssl_module --with-cc-opt=-O2 --with-compat

# 编译安装
make -j $(nproc) && make install

步骤 02.准备两台 Nginx 服务器,一台作为 stream 四层转发服务(192.168.10.2),另一台作为上游服务器(192.168.10.3)提供 http 服务。

stream 四层转发服务器配置示例:

go 复制代码
tee /usr/local/nginx/conf/nginx.conf <<'EOF'
worker_processes auto;
events {
  worker_connections  1024;
}

# stream 模块配置块
stream {
# 定义一个日志模式格式,加入了 $ssl_session_reused变量,以便记录会话是否被复用。
  log_format basic '$remote_addr:$remote_port "$realip_remote_addr:$realip_remote_port" [$time_local] '
'$protocol $status $bytes_sent $bytes_received '
'$session_time $ssl_protocol $ssl_session_reused';

# 开启日志文件描述符缓存,减少打开和关闭文件的开销
  open_log_file_cache max=1000 inactive=60s min_uses=2 valid=1m;

# 开启访问日志记录,注意这里为了测试未启用缓存,生产环境中推荐使用缓存。
  error_log /var/log/nginx/stream_error.log debug;
  access_log /var/log/nginx/stream_access.log basic;

# 定义一个四层TCP服务
  server {
# 监听 8443 端口,启用 ssl
    listen 8443 ssl; 

# 设置可信客户端IP地址,即真实地址将排除下述IP或IP段的地址。
    set_real_ip_from 127.0.0.1;
    set_real_ip_from 10.20.172.0/24;

# SSL 证书文件:server.weiyigeek.top
    ssl_certificate /usr/local/nginx/certs/server.crt;
    ssl_certificate_key /usr/local/nginx/certs/server.key;

# 支持的 SSL/TLS 协议版本
    ssl_protocols TLSv1.1 TLSv1.2 TLSv1.3;

# 支持的 SSL/TLS 加密套件
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-DH+CHACHA20:EECDH+CHACHA20-draft:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES;

# SSL 会话缓存
    ssl_session_cache shared:SSL:10m;
# SSL 会话超时时间
    ssl_session_timeout 10m;
# 优先使用服务器端支持的加密套件
    ssl_prefer_server_ciphers on;
# SSL 会话票据复用
    ssl_session_tickets on;

# 转发至上游服务器,此处仅为示例,实际应用中应替换为实际服务地址和端口。
    proxy_pass 10.20.172.213:8011;
  }
}
EOF

上游服务器(192.168.10.3)提供 http 服务配置示例:

go 复制代码
$ echo'https://www.weiyigeek.top' > /usr/local/nginx/html/index.html
$ vim /usr/local/nginx/conf.d/upstream.conf

# 模拟上游服务器其监听端口为8011
server {
  listen 8011;
  server_name _;
  charset utf-8;
  default_type text/plain;

# 静态文件服务配置
  location / {
    root  /usr/local/nginx/html;
    index index.html;
  }

# 返回当前时间戳作为响应内容
  location ^~ /api {
return 200 '$time_iso8601 $request_id $server_addr:$server_port $request_uri\n';
  }
}

步骤 03.启动 Nginx 服务并使用浏览器访问测试。

go 复制代码
$ nginx -t && nginx -s reload
nginx: the configuration file /usr/local/nginx/conf/nginx.conf syntax is ok
nginx: configuration file /usr/local/nginx/conf/nginx.conf test is successfu

# 硬解析设置或添加到内部DNS解析记录,以便浏览器能正确访问。
$ cat /etc/hosts
192.168.10.2 server.weiyigeek.top

在上游服务中使用 tcpdump 抓取 ens18 网卡的 8011 通讯包,命令 tcpdump -i ens18 port 8011 -A -s 0,然后在浏览器访问 https://server.weiyigeek.top:8443/index.html 以及 https://server.weiyigeek.top:8443/api 并查看 tcpdump 抓包结果。
weiyigeek.top-抓包验证 steam 四层转发图

步骤 04.另外,从我们记录的 stream_access.log 日志中可查看到客户端与服务端之间的SSL会话是否复用等信息。

go 复制代码
$ tail -f /var/log/nginx/stream_access.log
10.20.172.103:61065 "10.20.172.103:61065" [27/Jan/2026:11:52:41 +0800] TCP 200 0 0 60.004 "-" TLSv1.3 .   # 此处显示 . 表示会话未被复用,即客户端与服务端之前的连接是全新的。
10.20.172.103:61064 "10.20.172.103:61064" [27/Jan/2026:11:53:18 +0800] TCP 200 1273 2221 96.621 "-" TLSv1.3 . 
10.20.172.103:61501 "10.20.172.103:61501" [27/Jan/2026:11:59:20 +0800] TCP 200 0 0 60.001 "-" TLSv1.3 r   # 此处显示 r 表示会话被复用,即客户端与服务端之前的连接是复用的。
10.20.172.103:61499 "10.20.172.103:61499" [27/Jan/2026:11:59:25 +0800] TCP 200 253 783 65.002 "-" TLSv1.3 r # 此处显示 r 表示会话被复用,即客户端与服务端之前的连接是复用的。

$ tail -f /var/log/nginx/stream_error.log
2026/01/27 11:51:41 [info] 3093559#0: *35 proxy 10.20.172.214:41324 connected to 10.20.172.213:8011
2026/01/27 11:51:41 [info] 3093560#0: *36 proxy 10.20.172.214:41332 connected to 10.20.172.213:8011
2026/01/27 11:52:41 [info] 3093560#0: *36 upstream disconnected, bytes from/to client:0/0, bytes from/to upstream:0/0
2026/01/27 11:53:18 [info] 3093559#0: *35 upstream disconnected, bytes from/to client:2221/1273, bytes from/to upstream:1273/2221

weiyigeek.top-请求结果与请求日志查看图

至此,本小节实践了如何解析、验证客户端 SSL 证书,并将带有 SSL 的TCP 流剥离 SSL 层,并以裸 TCP 方式转发至上游服务器,此模式在实际场景中非常常见,尤其适用于非HTTP类协议的四层代理场景,应用范围广泛。


PREREAD 阶段

描述:上文讲解了如何在 SSL 阶段剥离 SSL 层,并以裸 TCP 方式转发至上游服务器,这虽然是最常用的场景,但是在某些场景下,例如上游和下游均使用 SSL 协议时,Nginx 需在不剥离 SSL 层的情况下,解析客户端Hello消息中的TLS版本、SNI域名和ALPN协议等信息。若直接转发带 SSL 的 TCP 流,则无法获取握手信息,若剥离 SSL 则改变了传输协议,无法满足透明转发需求,在这样的背景问题下,Nginx 提供了 stream SSL preread 模块来解决。
weiyigeek.top-SSL preread 模块使用场景图

◆ ssl_preread 模块

描述:stream_ssl_preread_module 模块默认未编译进 Nginx,需通过 --with-stream_ssl_preread_module 显式启用,在不剥离SSL的情况下解析的下游发来的SSL握手消息(Client Hello)消息中的TLS版本、SNI域名和ALPN协议等信息,并利用这些变量实现基于四层反向代理的上游服务动态选择,提升负载均衡灵活性,例如:

go 复制代码
# 根据服务器名称选择上游
map $ssl_preread_server_name$name {
  backend.example.com      backend1;
  default                  backend2;
}

# 根据协议选择上游
map $ssl_preread_alpn_protocols$proxy {
  ~\bh2\b           127.0.0.1:8001;
  ~\bhttp/1.1\b     127.0.0.1:8002;
  ~\bxmpp-client\b  127.0.0.1:8003;
}

# 根据SSL协议版本选择上游
map $ssl_preread_protocol$upstream {
""        ssh.example.com:22;
"TLSv1.2" new.example.com:443;
  default   tls.example.com:443;
}

# 例如 ssh 与 https 在同一端口,根据协议分流
server {
  listen      192.168.0.1:443;
  proxy_pass  $upstream;
  ssl_preread on;
}

特别注意:使用 stream_ssl_preread 模块时,向上游发送的是完整的SSL/TLS加密流,不可同时启用 stream_ssl 模块,若已执行SSL解密(进入明文TCP流),则 ssl_preread 阶段无法再获取原始 Client Hello 信息,并会导致冲突。

指令参数

  • • ssl_preread :是否允许在预读阶段从ClientHello消息中提取信息。
go 复制代码
Syntax: ssl_preread on | off;
Default: ssl_preread off;
Context: stream, server
  • • preread_buffer_size : 设置用于解析SSL握手数据的缓冲区大小,默认为 16KB,属于 stream_core_module 的一部分,因此可以在 stream 或 server 级别设置。
go 复制代码
Syntax: preread_buffer_size size;
Default: preread_buffer_size 16k;
Context: stream, server
  • • preread_timeout: 设置等待 Client Hello 完成的时间,默认 30 秒,属于 stream_core_module 的一部分,因此可以在 stream 或 server 级别设置。
go 复制代码
Syntax: preread_timeout timeout;
Default: preread_timeout 30s;
Context: stream, server

另外,该模块提供了以下变量,以供后续模块选择使用:

  • • $ssl_preread_protocol: 客户端支持的最高TLS版本(如 TLSv1.2, TLSv1.3),可用于根据安全等级选择不同上游服务器。

  • • $ssl_preread_server_name: 从 SNI(Server Name Indication)扩展中提取的服务器域名,用于基于域名的路由决策。

  • • $ssl_preread_alpn_protocols: 客户端建议使用的应用层协议(如 h2 表示 HTTP/2,http/1.1 等),可用于协议优化匹配。

示例演示

目的:使用 stream_ssl_preread 模块实现根据 $ssl_preread_server_name 获取客户端请求的域名,并反向代理此域名对应的上游服务器。

步骤 01.启用 stream_ssl_preread 模块加入到 Nginx 编译配置中,并重新编译安装。

go 复制代码
# 构建
./configure --prefix=/usr/local/nginx --user=nginx --group=nginx --sbin-path=/usr/sbin/nginx --conf-path=/usr/local/nginx/conf/nginx.conf --pid-path=/usr/local/nginx/nginx.pid --error-log-path=/var/log/nginx/logs/error.log --http-log-path=/var/log/nginx/logs/access.log --lock-path=/var/run/nginx.lock --modules-path=/usr/local/nginx/modules --with-http_stub_status_module --with-http_realip_module --with-http_v2_module --with-http_ssl_module --with-http_slice_module --with-stream --with-stream_realip_module --with-stream_ssl_module --with-stream_ssl_preread_module --with-cc-opt=-O2 --with-compat

# 编译安装
make -j $(nproc) && make install

步骤 02.配置 Nginx 以使用 stream_ssl_preread 模块提取的Client Hello 消息中关键变量实现基于域名的反向代理。

go 复制代码
tee /usr/local/nginx/conf/nginx.conf <<'EOF'
worker_processes auto;
events {
  worker_connections  1024;
}
# stream 模块配置块
stream {
# 定义一个日志模式格式。
  log_format ssl_basic '$remote_addr:$remote_port "$realip_remote_addr:$realip_remote_port" [$time_local] '
'$protocol $status $bytes_sent $bytes_received '
'$session_time ssl_preread=[$ssl_preread_server_name, $ssl_preread_protocol, $ssl_preread_alpn_protocols] '
'$ssl_client_i_dn $ssl_client_s_dn $ssl_client_v_end $ssl_curves';

# 开启日志文件描述符缓存,减少打开和关闭文件的开销
# open_log_file_cache max=1000 inactive=60s min_uses=2 valid=1m;

# 开启访问日志记录,注意这里为了测试未启用缓存,生产环境中推荐使用缓存。
  error_log /var/log/nginx/stream_error.log debug;
  access_log /var/log/nginx/stream_access.log ssl_basic;

# 反向代理到上游服务器,此处使用 map 根据域名选择不同的后端服务。
  map $ssl_preread_server_name$proxy_server {
    test.weiyigeek.top    www.weiyigeek.top:443;  # 作者首页
    blog.weiyigeek.top    blog.weiyigeek.top:443; # 作者博客
    default               10.20.172.213:8443;     # 其他情况默认指向默认服务
  }

# 定义一个四层TCP服务
  server {
# 监听 8443 端口
    listen 8443; 

# 设置可信客户端IP地址,即真实地址将排除下述IP或IP段的地址。
    set_real_ip_from 127.0.0.1;
    set_real_ip_from 10.20.172.0/24;

# 解析服务域名地址
    resolver 61.128.128.68 valid=30s;
    resolver_timeout 60s;

# 开启SSL预读阶段,以便提取客户端支持的TLS版本、SNI域名和ALPN协议等信息。
    ssl_preread on;

# 根据客户端请求的域名动态选择上游服务器
    proxy_pass $proxy_server;
  }
}
EOF

步骤 03.验证配置并重新启动 Nginx 服务,将测试域名使用硬解析方式指向 nginx 服务器IP地址 10.20.172.214 (根据实际情况更改) ,并使用浏览器分别访问 test.weiyigeek.top:8443 、 blog.weiyigeek.top:8443 以及 10.20.172.214:8443 测试配置是否生效。

go 复制代码
nginx -t && nginx -s reload

验证1:访问 https://test.weiyigeek.top:8443 ,正常情况下应能正常访问作者的首页,可在开发者工具【网络】选项卡,查看到未发生HTTP重定向,证明全程处于四层转发模式。
weiyigeek.top-验证1访问作者首页图

验证2:访问 https://blog.weiyigeek.top:8443 ,正常情况下应能正常访问作者的博客。
weiyigeek.top-验证2访问博客图

验证3:访问 https://10.20.172.214:8443 ,此时 $ssl_preread_server_name 变量将为空,因此正常情况下应能正常访问 10.20.172.213:8443 这个内部服务。
weiyigeek.top-验证3访问内部服务图

步骤 04.查看 Nginx 日志文件,验证是否正确记录了 $ssl_preread_server_name 以及协议版本,ALPN协议等信息。

go 复制代码
tail -f /var/log/nginx/stream_access.log
10.20.172.103:52166 "10.20.172.103:52166" [27/Jan/2026:16:07:26 +0800] TCP 200 1423439 6485 223.494 ssl_preread=[blog.weiyigeek.top, TLSv1.3, h2,http/1.1] - - - -  # 验证2
10.20.172.103:52302 "10.20.172.103:52302" [27/Jan/2026:16:07:53 +0800] TCP 200 3405 1900 0.016 ssl_preread=[, TLSv1.3, h2,http/1.1] - - - -                         # 验证3
10.20.172.103:52286 "10.20.172.103:52286" [27/Jan/2026:16:08:37 +0800] TCP 200 221095 3515 67.570 ssl_preread=[test.weiyigeek.top, TLSv1.3, h2,http/1.1] - - - -    # 验证1

weiyigeek.top-利用日志文件验证ssl_preread模块图

至此,作者带领大家一起利用 stream_ssl_preread 模块实现了在保持SSL加密流完整性的前提下,提取 Client Hello 中 $ssl_preread_protocol、$ssl_preread_server_name 和 $ssl_preread_alpn_protocols 三个关键变量,并利用 map 模块实现了基于 $ssl_preread_server_name 变量动态选择上游服务。在实际的生产场景中非常场景,尤其适用于非HTTP类协议的四层代理场景,基于TLS版本、域名或应用层协议的精细化上游服务选择,增强了四层反向代理的灵活性与智能化水平。

END

加入:作者【全栈工程师修炼指南】知识星球

『 全栈工程师修炼指南』星球,主要涉及全栈工程师(Full Stack Development)实践文章,持续更新包括但不限于企业SecDevOps和网络安全等保合规、安全渗透测试、编程开发、云原生(Cloud Native)、物联网工业控制(IOT)、人工智能Ai,从业书籍笔记,人生职场认识等方面资料或文章。

Q: 加入作者【全栈工程师修炼指南】星球后有啥好处?

✅ 将获得作者最新工作学习实践文章以及网盘资源。

✅ 将获得作者珍藏多年的全栈学习笔记(需连续两年及以上老星球友,也可单次购买)

✅ 将获得作者专门答疑学习交流群,解决在工作学习中的问题。

✅ 将获得作者远程支持(在作者能力范围内且合规)。

获取:作者工作学习全栈笔记

作者整理了10年的工作学习笔记(涉及网络、安全、运维、开发),需要学习实践笔记的看友,可添加作者微信或者回复【工作学习实践笔记】,当前价格¥299,除了获得从业笔记的同时还可进行问题答疑以及每月远程技术支持,希望大家多多支持,收获定大于付出!

知识推荐 往期文章

若文章对你有帮助,请将它转发给更多的看友,若有疑问的小伙伴,可在评论区留言你想法哟 💬!

相关推荐
谙弆悕博士1 小时前
debian快速换源
linux·运维·windows·debian·下载·换源·网络问题
Yang96111 小时前
鼎讯信通热剥钳实测:大芯径光纤如何做到无损剥除
网络
.道阻且长.1 小时前
Linux基本开发工具:编译器gcc/g++
linux·运维·服务器·职场和发展
Decay__7 小时前
05_MySQL 常用 SQL 大全 —— 诊断、管理、优化辅助 SQL 与三大系统库速查
运维·数据库·sql·mysql·架构
贞明电子9 小时前
工业三色报警灯怎么接入项目系统?开关量与RS485控制应用指南
运维·网络·微信公众平台·贞明电子
明天也要努力学习10 小时前
HTTP 协议的基本格式和 fiddler 的用法
网络协议·http·fiddler
HideF10 小时前
Linux
linux·服务器·网络
万联WANFLOW10 小时前
海外网络故障怎么排查?Cloudflare 三季度断网报告解读与 mtr 实战
运维·网络·故障分析
sbjdhjd10 小时前
网工进阶笔记:H3CSE 园区网全栈技术与协议汇总
网络·笔记·云计算·ssh·网络工程·vlan·aaa
Ruiery12 小时前
Linux 6.6内核内存管理深度解析(十三):slab/slub 分配器
linux·运维·服务器