根据 OSI 七层模型划分,其中这里四层指OSI模型中的传输层,主要包含TCP和UDP协议。之前介绍的七层协议(如HTTP、HTTP/2、WebSocket、gRPC)均运行在TCP之上,因此四层协议相对简单,学习时可对照七层反向代理的指令与流程进行理解。

Nginx 通过 Stream 相关模块实现四层反向代理,它是一种不同于 http 模块的新模块。本小节将详细讲解stream模块的七个处理阶段,和 Stream 模块结构及对应阶段模块指令。
前面我们介绍过 http 模块有11个处理阶段,而 stream 模块仅有7个处理阶段,此七个阶段对应模块如下表:
| 阶段 |
模块 |
| POST_ACCEPT |
realip |
| PREACCESS |
limit_conn |
| ACCESS |
access |
| SSL |
ssl |
| PREREAD |
ssl_preread |
| CONTENT |
return, stream_proxy |
| LOG |
access_log |
对应于上表,我们逐一讲解每个阶段对应模块的指令与功能。
POST_ACCEPT 阶段:连接建立后触发,常被称为accept后的处理阶段。
realip 模块在此阶段通过proxy_protocol获取客户端真实IP地址,与http模块中POST_READ阶段的 realip 模块功能相似,但因TCP无HTTP头部,需依赖proxy_protocol传递真实IP。
PREACCESS 阶段:连接建立后访问触发,用于在访问控制之前进行前置预处理,例如限制连接数。
- 含义与
http模块中的preaccess一致。在http模块中区分limit_conn(基于连接)和limit_req(基于请求)。所以对于stream模块,仅存在limit_conn,无limit_req概念,因其面向TCP连接而非HTTP请求。
ACCESS 阶段: 连接建立后访问触发,用于控制访问权限,例如基于IP地址的访问控制。
- 功能与
http模块中的access阶段完全一致,支持allow和deny指令,可根据客户端IP地址允许或拒绝访问。
SSL 阶段:在SSL握手完成后触发,用于配置SSL证书和密钥。
- 为
stream模块新增阶段,http模块中不存在。用于处理SSL/TLS握手请求,由于 SSL位于TCP之上,因此可在stream上下文中使用 SSL。
PREREAD 阶段: 名称不够贴切,亦可理解为 ssl_preread ,用于在SSL握手前读取数据。
- 在 SSL 握手过程中,客户端发送
ClientHello消息,ssl_preread模块可从中提取信息并设为变量供后续使用。
CONTENT 阶段: 处理实际的数据传输,例如反向代理。
- 包含
stream_proxy模块,实现反向代理功能。
- 包含
return模块,可直接返回字符串或变量值,类似于http模块中的return指令。
LOG 阶段:记录日志,类似于http模块的log阶段。
- 与
http模块的log阶段相似,用于记录access_log,因TCP协议信息量少,日志记录内容也较少。
至此,我们简单介绍了stream模块的七个处理阶段,后续将会分别演示实践。
Nginx Stream 四层核心模块指令介绍
在 Nginx 中 ngx_stream_core_module 模块 (自1.9.0版本起可用) 默认是不构建的,若要启用需在构建时添加 --with-stream 参数。例如,作者使用 nginx/1.29.0 版本为例进行编译安装。
# 构建
./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-cc-opt=-O2 --with-compat
# 编译安装
make -j $(nproc) && make install

同样,Nginx stream 模块的核心指令可类比于http块,位于全局配置层级。stream 块下可定义 server 块。server 块中不再有 location 概念,因 location 是针对URL路径的,不适用于TCP。简单示例如下所示:
worker_processes auto;
error_log /var/log/nginx/error.log info;
events {
worker_connections 1024;
}
# 指定全局 stream 模块配置
stream {
# 定义上游服务器组,类似于 http 模块中的 upstream 块。
upstream backend {
hash $remote_addr consistent;
server backend1.example.com:12345 weight=5;
server 127.0.0.1:12345 max_fails=3 fail_timeout=30s;
server unix:/tmp/backend3;
}
upstream dns {
server 192.168.0.1:53535;
server dns.example.com:53;
}
# 定义 server 块,类似于 http 模块中的 server 块。
server {
listen 12345;
proxy_connect_timeout 1s;
proxy_timeout 3s;
proxy_pass backend;
}
server {
listen 127.0.0.1:53 udp reuseport;
proxy_timeout 20s;
proxy_pass dns;
}
server {
listen [::1]:12345;
proxy_pass unix:/tmp/stream.socket;
}
}
参考文档:https://nginx.org/en/docs/stream/ngx_stream_core_module.html
指令参数
stream : 提供配置文件上下文,在其中指定流服务器指令,对比等价 http 块。
Syntax: stream { ... }
Default: —
Context: main
server:定义流服务器块,类似于HTTP模块中的server块,设置虚拟服务器配置,基于IP(基于IP地址)和基于名称(基于TLS服务器名称指示扩展(SNI, RFC 6066))。
Syntax: server { ... }
Default: —
Context: stream
-
server_name:设置基于名称的虚拟服务器的服务器名,使用与 http 模块中的server_name指令相同。
Syntax: server_name name ...;
Default: server_name "";
Context: server
# 示例
server_name example.com www.example.com; # 第一个名称成为主服务器名称。
server_name example.com *.example.com www.example.*; # 通配符名称
server_name .example.com;
server_name www.example.com ~^www\d+\.example\.com$; # 正则表达式名称
server_name ~^(www\.)?(.+)$; # 利用正则元组捕获变量名称
# 优先级说明:
1. 精确匹配的名称。
2. 通配符匹配的名称,最长匹配优先(例如,"*.example.com" 比 "*.sub.example.com" 更具体)。
3. 正则表达式匹配的名称,最先定义的优先级最高。
4. 所有其他情况,以第一个被匹配到的名称为主。
-
listen:监听端口或 UNIX sockets,支持TCP和UDP协议,支持 proxy_protocol 选项,用于接收携带真实客户端IP的协议数据。
Syntax: listen address:port [default_server] [ssl] [udp] [proxy_protocol] [setfib=number] [fastopen=number] [backlog=number] [rcvbuf=size] [sndbuf=size] [accept_filter=filter] [deferred] [bind] [ipv6only=on|off] [reuseport] [so_keepalive=on|off|[keepidle]:[keepintvl]:[keepcnt]];
Default: —
Context: server
# 参数说明,下述参数主要应用于 Unix
setfib=number : 设置路由域的标识符,仅在FreeBSD上有效。
fastopen=number : 为监听套接字启用TCP Fast Open,并限制尚未完成三次握手的连接队列的最大长度。
backlog=number :该参数限制挂起连接队列的最大长度,默认情况下在 FreeBSD,DragonFly BSD和macOS上,backlog设置为-1,在其他平台上设置为511。
rcvbuf=size : 用于监听套接字设置接收缓冲区大小(SO_RCVBUF选项)。
sndbuf=size :用于监听套接字设置发送缓冲区大小(SO_SNDBUF选项)。
accept_filter=filter :用于监听套接字设置接受过滤器的名称(SO_ACCEPTFILTER选项),适用于FreeBSD和NetBSD 5.0+。
deferred :指示使用延迟的accept() (TCP_DEFER_ACCEPT 选项) 在Linux(1.25.5)引入。
ipv6only=on|off :仅在IPv6上接受连接,或在IPv4和IPv6上都接受。
reuseport :为每个工作进程创建一个单独的监听套接字,在Linux 3.9+、FreeBSD 12.0+和DragonFly BSD 5.0+上支持。
so_keepalive=on|off|[keepidle]:[keepintvl]:[keepcnt] :启用听套接字配置“TCP保持连接”行为或禁用TCP keepalive消息,并设置keepalive参数,例如:so_keepalive=30m::10 (keepidle=30m, keepintvl=1s, keepcnt=10)
# 示例演示
listen 127.0.0.1:12345;
listen *:12345;
listen 12345; # 同上
listen localhost:12345;
listen unix:/var/run/nginx.sock;
listen 127.0.0.1:12345-12399; # 监听多个端口范围
listen 12345-12399;
server_names_hash_bucket_size:设置服务器名称哈希表存储桶的大小,默认为32/64/128字节。
Syntax: server_names_hash_bucket_size size;
Default: server_names_hash_bucket_size 32|64|128;
Context: stream
server_names_hash_max_size:设置服务器名称哈希表的最大大小,默认为512字节。
Syntax: server_names_hash_max_size size;
Default: server_names_hash_max_size 512;
Context: stream
tcp_nodelay : 启用或禁用TCP的使用 TCP_NODELAY 选项。该选项对客户机和代理服务器连接都是启用的。
Syntax: tcp_nodelay on | off;
Default: tcp_nodelay on;
Context: stream, server
-
resolver : 配置名称服务器,用于将上游服务器的名称解析为地址。
Syntax: resolver address ... [valid=time] [ipv4=on|off] [ipv6=on|off] [status_zone=zone];
Default: —
Context: stream, server
# 例如:指定DNS服务器地址,并设置有效时间为30秒,以覆盖接受到的 TTL 值 。
resolver 127.0.0.1 [::1]:5353 valid=30s;
# 温馨提示:status_zone 参数(1.17.1)表示统计指定区域内DNS服务器的请求和响应信息,是在商业订阅版本中使用。
resolver_timeout : 设置名称解析超时时间,默认为30秒。
Syntax: resolver_timeout time;
Default: resolver_timeout 30s;
Context: stream, server
variables_hash_bucket_size :设置变量哈希表的桶大小。
Syntax: variables_hash_bucket_size size;
Default: variables_hash_bucket_size 64;
Context: stream
variables_hash_max_size : 设置变量哈希表的最大大小。
Syntax: variables_hash_max_size size;
Default: variables_hash_max_size 1024;
Context: stream
另外,ngx_stream_core_module 核心模块还内置一些变量,用于在其它协作模块中使用。
(1) 获取客户端与服务端信息变量
$binary_remote_addr : 客户端的二进制地址。
$remote_addr : 客户端地址。
$remote_port : 客户端端口。
$server_addr : 接受连接的服务器地址。计算这个变量的值通常需要一次系统调用。为了避免系统调用,listen 指令必须指定地址并使用bind参数。
$server_port : 接受连接的服务器的端口。
$proxy_protocol_addr : 接收到的代理协议头中的客户端地址。
$proxy_protocol_port :接收到的代理协议头中的客户端端口。
$proxy_protocol_server_addr : 代理协议头中的服务器地址。
$proxy_protocol_server_port : 代理协议报头中的服务器端口。
-
$proxy_protocol_tlv_name : 获取TLV类型为“NAME”的代理协议头中的值。SSL TLV也可以通过TLV类型名称或其数值访问,两者都以SSL作为前缀_:
$proxy_protocol_tlv_ssl_version
$proxy_protocol_tlv_ssl_0x21
# 常规TLV类型名称和数值如下所示:
alpn (0x01) - upper layer protocol used over the connection
authority (0x02) - host name value passed by the client
unique_id (0x05) - unique connection id
netns (0x30) - name of the namespace
ssl (0x20) - binary SSL TLV structure
# SSL 类型名称和数值如下所示:
ssl_version (0x21) - SSL version used in client connection
ssl_cn (0x22) - SSL certificate Common Name
ssl_cipher (0x23) - name of the used cipher
ssl_sig_alg (0x24) - algorithm used to sign the certificate
ssl_key_alg (0x25) - public-key algorithm
ssl_verify - certificate verification result,如果客户端提供证书并成功验证,则为零,否则为非零
(2) 获取通信传输信息及响应状态变量
$protocol: 获取与客户端通信的协议 TCP或UDP (1.11.4)。
$bytes_received:从客户端接收到的字节数。
$bytes_sent: 发送到客户端的字节数。
$connection: 连接序号。
$session_time: 会话持续时间(秒),分辨率为毫秒(1.11.4)。
$status : 会话状态(1.11.4)。状态码含义与http模块相似,其常见取值如下:
- [x] 200: 会话正常结束。
- [x] 400: 客户端数据无法解析,例如
proxy_protocol 格式错误。
- [x] 403: 禁止访问,例如
access 模块限制IP或 limit_conn 触发。
- [x] 500: 内部服务器错误。
- [x] 502: 无法连接上游服务,例如 网关错误。
- [x] 503: 服务不可用,例如当访问受到连接数量的限制时。
(3) 获取其它信息的变量
$hostname : 所在服务器的主机名,与 hostname 命令输出一致主机名。
$nginx_version : nginx 版本
$pid : 所属 worker 工作进程的 PID()
$msec : 时间戳(自1970年1月1日以来的秒数,小数部分为毫秒)
$time_iso8601: 使用 ISO 8601 标准格式的本地时间,例如:2018-11-14T15:55:37+08:00
$time_local: 通用日志格式的本地时间,例如:14/Nov/2018:15:55:37 +0800
牛刀小试
步骤 01.在源码编译后的 Nginx 配置文件中,添加一个 stream 块并在其中,创建一个四层 stream 服务以供测试:
tee /usr/local/nginx/conf/nginx.conf <<'EOF'
worker_processes auto;
error_log /var/log/nginx/error.log debug;
events {
worker_connections 1024;
}
# stream 模块配置块
stream {
# 定义一个四层TCP服务,监听 8090 端口
server {
listen 10.20.172.214:8090;
return 'Hello World, Nginx Stream!
hostname: $hostname
pid: $pid
nginx_version: $nginx_version
protocol: $protocol
remote_addr: $remote_addr
remote_port: $remote_port
server_addr: $server_addr
server_port: $server_port
bytes_received: $bytes_received
bytes_sent: $bytes_sent
session_time: $session_time
status: $status
msec: $msec
time_iso8601: $time_iso8601\n';
}
}
EOF
步骤 02.重新加载 Nginx 服务,在客户端使用打开 cmd 或 shell,使用 telnet 命令测试搭建的四层TCP服务。TCP 连接建立即触发处理流程,无需发送请求,且在 return 指令执行后立即关闭连接,不再进行后续代理操作。
$ 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 successful
# 在另外一台客户端测试四层TCP服务连接:
$ telnet 10.20.172.214 8090
Trying 10.20.172.214...
Connected to 10.20.172.214.
Escape character is '^]'.
Hello World, Nginx Stream!
hostname: weiyigeek_top
pid: 2187344
nginx_version: 1.29.0
protocol: TCP
remote_addr: 10.20.176.212
remote_port: 23648
server_addr: 10.20.172.214
server_port: 8090
bytes_received: 0
bytes_sent: 0
session_time: 0.000
status: 000
msec: 1768893874.932
time_iso8601: 2026-01-20T15:24:34+08:00
Connection closed by foreign host.

至此,本小节介绍了 ngx_stream_core_module 模块的指令和变量,并演示一个简单的四层反向代理,利用 CONTENT 阶段的 return 指令返回客户端连接请求与服务端响应的一些测试信息。

END
希望这篇关于Nginx四层反向代理的实践解析,能帮助大家理解TCP/UDP层面的负载均衡配置。在大流量场景下,四层代理因其处理效率高,常作为高性能架构的核心组件。在实际部署中,结合恰当的监控手段对连接数和会话状态进行跟踪,是保障服务稳定性的关键。深入学习TCP/IP等底层网络协议,将有助于更好地进行内核调优与故障排查。欢迎在云栈社区继续交流相关技术细节。