
知识是人生的灯塔,只有不断学习,才能照亮前行的道路
📢 大家好,我是 WeiyiGeek,一名深耕安全运维开发(SecOpsDev)领域的技术从业者,致力于探索DevOps与安全的融合(DevSecOps),自动化运维工具开发与实践,企业网络安全防护,欢迎各位道友一起学习交流、一起进步 🚀,若此文对你有帮助,一定记得点个关注⭐与小红星❤️或加入到作者知识星球『 全栈工程师修炼指南』,转发收藏学习不迷路 😋 。
CONTENT 阶段
描述:在 CONTENT 阶段,Nginx 会根据前面的处理结果决定是否继续执行后续的模块或直接返回给客户端,该阶段最常用的两个模块是 ngx_stream_return_module 和 ngx_stream_proxy_module,它们都是默认编译进 Nginx 的模块。
本小节中,将详细介绍这两个模块的指令参数及其使用方法,并通过 stream proxy 模块进行上下游流量控制、SSL配置及 proxy protocol 协议传递客户端真实IP地址的实践。
好了,实践出真知!
温馨提示:若文章代码块中存在乱码或不能复制,请联系作者,也可通过文末的阅读原文链接,加入知识星球中阅读,原文链接:https://articles.zsxq.com/id_t5z28zcqv7us.html
◆ return 模块
描述:ngx_stream_return_module 模块提供了一个 return 指令,用于在 CONTENT 阶段终止处理流程,它可以返回一个固定的字符串或变量,也可以返回一个特定的状态码给用户,然后关闭连接。
go
Syntax: return value;
Default: ---
Context: server
# 示例:在 server 块中使用 return 指令返回当前时间戳
server {
listen 12345;
return$time_iso8601;
}
◆ proxy 模块
描述: ngx_stream_proxy_module 模块使 Nginx 能够作为四层(TCP/UDP)进行反向代理工作,支持与上游连接使用 TLS/SSL 协议,支持自动加入 proxy_protocol 协议格式转发给上游。另外,该模在前面小节的学习中我们已经有所涉及,例如 proxy_pass 指令就是该模块的核心功能,想必看友们都不陌生吧!
虽然 stream proxy 可直接转发 TCP/UDP 流量,但在实际应用中,尤其是在涉及到HTTP协议的场景下,当缺乏HTTP头部信息(如 X-Forwarded-For)的情况下,如何将客户端真实IP传递给上游服务成为关键问题,当然流行的 Nginx 也提供了解决方法,那就是我们前面介绍过的 proxy_protocol 协议,在配置中启用 proxy_protocol 指令,从而保证客户端的真实IP不被Nginx服务器更改或丢失。
下面是 stream proxy 模块与http proxy 模块关键指令对应表,我将介绍一些关键指令参数,其它指令参数还请查阅官方文档。
weiyigeek.top-stream proxy与http proxy模块对应表图
指令参数
对上游服务器反向代理:
- •
proxy_bind指令用于设置代理服务器绑定到上游服务器的本地IP地址,这对于某些需要特定源IP的场景非常有用。 若设置为transparent,则允许内核处理IP伪装,通常与proxy_protocol配合使用。 若设置为off,则不绑定任何本地
go
Syntax: proxy_bind address [transparent] | off;
Default: ---
Context: stream, server
# 示例
proxy_bind $remote_addr transparent;
- •
proxy_pass指令用于指定代理服务器将请求转发到的上游服务器的地址。
go
Syntax: proxy_pass address;
Default: ---
Context: server
# 示例
proxy_pass localhost:12345;
proxy_pass unix:/tmp/stream.socket;
proxy_pass $upstream;
- •
proxy_protocol指令用于启用或禁用 PROXY protocol 支持,这对于需要将客户端的真实IP地址传递给上游服务器的情况非常有用。
go
Syntax: proxy_protocol on | off;
Default: proxy_protocol off;
Context: stream, server
- •
proxy_socket_keepalive指令用于启用或禁用代理服务器与上游服务器的 TCP keepalive 功能,这对于保持长连接非常有用。
go
Syntax: proxy_socket_keepalive on | off;
Default: proxy_socket_keepalive off;
Context: stream, server
# 参数说明
on 允许在空闲时重用TCP连接
off 则在每次请求后关闭连接
- •
proxy_half_close: 允许或禁止独立关闭TCP连接,如果开启此功能,TCP代理会话会一直保持,直到通信双方都主动关闭连接。场景:如果你的应用涉及大文件传输或老旧的TCP服务,建议开启此功能
go
Syntax: proxy_half_close on | off;
Default: proxy_half_close off;
Context: stream, server
# 概念
半关闭(Half-Close): TCP协议允许你只关闭"发送"方向,但保留"接收"方向。这就好比在电话里说:"我说完了(关闭我的麦克风),但你继续说,我还在听(保留我的听筒)
# A. 如果开启(Enabled)
"proxying over TCP will be kept until both sides close the connection."
行为: 代理会严格遵循TCP的半关闭语义。
场景: 假设客户端先发完了数据,它发送了一个FIN包(表示"我发完了")给代理。如果开启此功能,代理不会立即断开与后端服务器的连接,而是会把这个FIN转发给服务器,并继续从服务器接收数据。
结果: 只有当客户端和服务器都分别发送了FIN(表示双方都发完了),代理才会彻底断开两端的连接。
优点: 符合标准TCP协议,支持复杂的交互场景。
缺点: 增加了代理的复杂性,需要维护更多的连接状态。
# B. 如果禁用(Disabled)
行为: 代理会进行"优化"或"简化",不支持半关闭。
场景: 一旦代理发现任何一端(比如客户端)关闭了写通道(发送FIN),它会立即断开与另一端(服务器)的连接,或者直接关闭整个连接。
结果: 连接提前终止。
影响: 这可能导致服务器还没来得及发送完最后的响应数据就被强制切断,导致数据丢失或截断。
对上游、上游服务器连接时间控制:
- •
proxy_connect_timeout指令设置代理服务器与上游服务器建立连接的超时时间。 若在此时间内未成功连接,则关闭连接。
go
Syntax: proxy_connect_timeout time;
Default: proxy_connect_timeout 60s;
Context: stream, server
- •
proxy_timeout指令设置客户端或代理服务器连接上两次连续读或写操作之间的超时时间,若单位时间内没数据传输,则关闭!
go
Syntax: proxy_timeout timeout;
Default: proxy_timeout 10m;
Context: stream, server
对上游、下游流量控制:
由于 Nginx Stream 在四层反向代理模式下使用极小的内存缓存,不涉及刀磁盘缓存 body 等操作,因此其对上游、下游流量控制主要通过 proxy_download_rate 和 proxy_upload_rate 指令来实现。这两个指令允许你限制代理服务器与上游或下游之间的数据传输速率,这对于控制带宽使用和防止网络拥堵非常有用。
- •
proxy_buffer_size定义内存中用于转发的缓冲区大小, 此值同时影响上游和下游缓冲区(两个独立缓冲区), 较小的缓冲区会导致间接限速。
go
Syntax: proxy_buffer_size size;
Default: proxy_buffer_size 16k;
Context: stream, server
- •
proxy_download_rate指令用于限制上游到代理服务器的下行速率,从而控制下游与代理服务器之间的数据速度。
go
Syntax: proxy_download_rate rate;
Default: proxy_download_rate 0;
Context: stream, server
- •
proxy_upload_rate指令用于限制下游到代理服务器的上行速率,从而控制数据从代理服务器流向上游的速度。
go
Syntax: proxy_upload_rate rate;
Default: proxy_upload_rate 0;
Context: stream, server
# 示例1:限制下行速率为100kbps,上行速率为200kbps
proxy_download_rate 100k;
proxy_upload_rate 200k;
# 示例2.根据特定条件限制速率的情况下,可以使用 map 模块配合使用。
map $slow$rate {
1 100k;
2 200k;
}
proxy_download_rate $rate;
proxy_upload_rate $rate;

weiyigeek.top-限制读取速率图
反向代理错误处理与重试机制:
- •
proxy_next_upstream指令用于在代理服务器与上游服务器之间的连接失败时,决定是否尝试连接到下一个上游服务器,在多上游服务场景下生效。
go
Syntax: proxy_next_upstream on | off;
Default: proxy_next_upstream on;
Context: stream, server
- •
proxy_next_upstream_tries指令设置在尝试连接到下一个上游服务器之前的重试次数。若设置为0,则无限次重试直到连接成功或达到proxy_next_upstream_timeout超时时间。
go
Syntax: proxy_next_upstream_tries number;
Default: proxy_next_upstream_tries 0;
Context: stream, server
- •
proxy_next_upstream_timeout指令设置在尝试连接到下一个上游服务器之前的超时时间。若在此时间内未成功连接,则关闭连接并返回错误给客户端, 若设置为 0 则关闭此限制。
go
Syntax: proxy_next_upstream_timeout time;
Default: proxy_next_upstream_timeout 0;
Context: stream, server
实践:使用 Proxy 模块进行 TCP 四层反向代理
-
• 场景1:客户端请求自带
proxy protocol头部,承载 HTTP 协议,此时proxy protocol位于最前端,随后才是 HTTP 请求内容。在上游服务器http块中使用listen指令,并添加proxy_protocol关键字剥离头部,剥离后可获取原始客户端的真实IP地址。 -
• 场景2:客户端请求不含
proxy protocol头部,Nginx 代理服务器在向上游转发时自行添加proxy protocol头部,经过各类 SLB 负载均衡设备,最后在上游服务器http块中使用listen指令,并添加proxy_protocol关键字剥离头部,剥离后可获取原始客户端的真实IP地址。
weiyigeek.top-Stream Proxy 模块实践图
由上图可知,两种场景的核心区别在于 proxy_protocol 指令的状态,当设置为 on 时,即使上游未携带proxy protocol,Nginx 也主动添加自身IP作为真实地址,而 off 状态则不做任何处理。
步骤 01.准备两台虚拟机,一台作为 Nginx 代理服务器(10.20.172.214),另一台作为上游服务器(10.20.172.213),上游服务器的 Nginx 配置如下:
go
# 上游服务器
$ /usr/local/nginx/conf/nginx.conf
http {
....
# 模拟上游服务器其监听端口为 8010 ,并开起 proxy_protocol 解析
server {
listen 8010 proxy_protocol;
# 任意主机名
server_name _;
# 设置字符集为utf-8
charset utf-8;
# 设置默认响应类型为文本
default_type text/plain;
# 错误日志
error_log logs/upstream_error.log debug;
# 根目录
location / {
root /usr/local/nginx/html;
index index.html;
}
# 模拟接口响应,返回通过 proxy_protocol 获取的真实客户端IP地址
location ^~ /api {
return 200 '$time_iso8601 $server_addr:$server_port $request_uri $remote_addr:$remote_port [ $proxy_protocol_addr:$proxy_protocol_port ]\n';
}
}
...
}
步骤 02.在代理服务器(10.20.172.214)上配置 Nginx Stream,配置如下所示:
go
# Nginx 代理服务器
$ vim /usr/local/nginx/conf/nginx.conf
worker_processes auto;
events {
worker_connections 1024;
}
# stream 模块配置块
stream {
# 定义一个日志模式格式。
log_format basic '$remote_addr:$remote_port "$realip_remote_addr:$realip_remote_port" [$time_local] '
'$protocol $status $bytes_sent $bytes_received '
'$session_time proxy_protocol=[$proxy_protocol_addr:$proxy_protocol_port] ';
# 开启日志文件描述符缓存,减少打开和关闭文件的开销
# 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 {
# 监听 8090 端口
listen 8090;
# 设置可信客户端IP地址,即真实地址将排除下述IP或IP段的地址。
set_real_ip_from 127.0.0.1;
# 根据客户端请求的域名动态选择上游服务器
proxy_pass 10.20.172.213:8010;
# 启用或禁用 proxy_protocol 协议头添加功能,这里模拟场景一先禁用,在后续场景二启用,对比其差异。
# proxy_protocol on;
}
}
步骤 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 successful
步骤 04.在客户端(10.20.172.103)上使用浏览器与 telnet 命令验证场景1,首先使用浏览器访问 http://10.20.172.214:8090/api 此时未携带 proxy_protocol 协议头,由于上游需要解析 proxy_protocol 头才正常响应数据,否则如下图所示,显示连接被重置:
go
# 代理服务器访问日志
$ tail -f stream_access.log
10.20.172.103:49334 "10.20.172.103:49334" [29/Jan/2026:15:11:19 +0800] TCP 200 0 396 0.000 proxy_protocol=[-:-]
10.20.172.103:49335 "10.20.172.103:49335" [29/Jan/2026:15:11:19 +0800] TCP 200 0 396 0.000 proxy_protocol=[-:-]
# 上游服务器错误日志
$ tail -f upstream_error.log
2026/01/29 15:12:27 [error] 3077128#3077128: *29 broken header: "GET /api HTTP/1.1"while reading PROXY protocol, client: 10.20.172.214, server: 0.0.0.0:8010
2026/01/29 15:12:27 [error] 3077128#3077128: *30 broken header: "GET /api HTTP/1.1"while reading PROXY protocol, client: 10.20.172.214, server: 0.0.0.0:8010
weiyigeek.top-使用浏览器访问图
同样,我们使用 telnet 命令进行测试,不同的是这次我们需手动输入 proxy_protocol 协议头,以及要请求的 url 路径,将如下图所示,成功响应 api 接口数据,并且在上游服务器中可以看到从 proxy_protocol 协议头取得的真实的客户端IP地址及端口。
go
$telnet 10.20.172.214 8090
Trying 10.20.172.214...
Connected to 10.20.172.214.
Escape character is '^]'.
# 手动输入 proxy_protocol 协议头
PROXY TCP4 202.12.144.236 10.20.172.214 58685 8090
# 手动输入请求,HOST 字段根据实际场景填写,若设置了虚拟主机域名,则需添加对于域名,反之如下可使用任意字符串即可。
GET /api HTTP/1.1
HOST: localhost
[回车]
# 响应头
HTTP/1.1 200 OK
Server: openresty/1.27.1.1
Date: Thu, 29 Jan 2026 07:23:20 GMT
Content-Type: text/plain; charset=utf-8
Content-Length: 90
Connection: keep-alive
# 响应内容:时间 服务器地址:端口 请求路径 代理请求客户端IP:端口 [ proxy_protocol=真实的客户端IP地址:端口 ]
2026-01-29T15:31:34+08:00 10.20.172.213:8010 /api 10.20.172.214:39366 proxy_protocol=[ 202.12.144.236:58685 ]
# 重点关注上行内容,其中 202.12.144.236:58685 即为从 proxy_protocol 协议头取得的真实的客户端IP地址及端口。
之后,再查看 Nginx 代理服务器日志,可以看到已经成功转发了请求。
go
$ tail -n2 stream_access.log
10.20.172.103:49903 "10.20.172.103:49903" [29/Jan/2026:15:23:18 +0800] TCP 200 259 92 75.710 proxy_protocol=[-:-]
$ tail -n 3 stream_error.log
2026/01/29 15:22:02 [info] 3377401#0: *47 client 10.20.172.103:49903 connected to 0.0.0.0:8090
2026/01/29 15:22:02 [info] 3377401#0: *47 proxy 10.20.172.214:52292 connected to 10.20.172.213:8010
2026/01/29 15:23:18 [info] 3377401#0: *47 upstream disconnected, bytes from/to client:92/259, bytes from/to upstream:259/92
# 上游代理服务器日志,也可查看到上游响应给代理客户端的数据
$ tail -f access.log
10.20.172.214 - - [29/Jan/2026:15:23:20 +0800] "GET /api HTTP/1.1" 200 90 "-""-"
weiyigeek.top-使用 telnet 访问图
步骤 05.现在问题来了,浏览器访问并不会自动添加 proxy_protocol 协议头,而通过终端访问每次请求都需要手动输入 proxy_protocol 协议头显然是不现实的,那么我们该如何解决呢?很简单,那便是使用场景二中的方式,在 Nginx 代理服务器配置文件中启用 proxy_protocol 功能即可:
go
# 修改Nginx 代理服务器配置文件,启用 proxy_protocol 功能。
$ vim /usr/local/nginx/conf/nginx.conf
# 启用 proxy_protocol 指令
proxy_protocol on;
# 重启 Nginx 服务
nginx -s reload
再次,使用浏览器访问 http://10.20.172.214:8090/api 此时无需携带 proxy_protocol 协议头,即可正常响应数据,并且在上游服务器中可以看到从 proxy_protocol 协议头取得的真实IP地址和端口。
weiyigeek.top-使用proxy_protocol指令效果图
至此,本文介绍了 CONTENT 阶段中两个常用模块,一个是 return 模块,一个是 proxy_protocol 模块,重点介绍了 stream 在 TCP 四层反向代理中上下游限速、各类超时与重试机制的指令参数,通过实战案例演示了 proxy protocol 协议在两种典型场景下的应用,在掌握这些配置对于在四层反向代理中准确传递客户端真实IP具有重要意义,当然也对后续课程的学习有帮助,各位道友实践起来吧。

END
加入:作者【全栈工程师修炼指南】知识星球
『 全栈工程师修炼指南』星球,主要涉及全栈工程师(Full Stack Development)实践文章,持续更新包括但不限于企业SecDevOps和网络安全等保合规、安全渗透测试、编程开发、云原生(Cloud Native)、物联网工业控制(IOT)、人工智能Ai,从业书籍笔记,人生职场认识等方面资料或文章。
Q: 加入作者【全栈工程师修炼指南】星球后有啥好处?
✅ 将获得作者最新工作学习实践文章以及网盘资源。
✅ 将获得作者珍藏多年的全栈学习笔记(需连续两年及以上老星球友,也可单次购买)
✅ 将获得作者专门答疑学习交流群,解决在工作学习中的问题。
✅ 将获得作者远程支持(在作者能力范围内且合规)。

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

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