nginx 加了真实 IP,我的 VLESS 全挂了:一次 PROXY protocol 不对称引发的排查实录
nginx 加了真实 IP,我的 VLESS 全挂了:一次 PROXY protocol 不对称引发的排查实录
凌晨两点,客户端全部掉线。v2rayN 一片红,sing-box 日志里刷着 parse VLESS header failed。
我什么都没改。就加了三行 nginx。
set_real_ip_from 0.0.0.0/0;、real_ip_header X-Forwarded-For;,还有一个 proxy_protocol on;。
本意挺好:日志里想看到访客的真实来源,而不是清一色的 127.0.0.1。结果这三行直接把节点干废了。
下面是我那晚从头查到尾的过程。
先搞清楚:真实 IP 是怎么传的
nginx 给后端传真实 IP,就两条路。
一条走 HTTP 头。 X-Forwarded-For、X-Real-IP,配合 real_ip 模块用。nginx 从头里读出真实 IP,把 $remote_addr 替换掉。
但 real_ip 是 http 模块,只管七层。VLESS 走裸 TCP 的时候(最常见的形态),转发它的是 nginx 的 stream 模块。real_ip 在这条链路上压根不存在,你配了等于白配。
另一条走 PROXY protocol。 这是给四层准备的。nginx 往 sing-box 方向先发一行协议头:
PROXY TCP4 8.8.8.8 1.2.3.4 54321 443\r\n
后面跟着的才是原始 TCP 流。sing-box 读完这行,就知道真实客户端是谁。
问题就出在这条路上。PROXY protocol 两边都得开,只开一边,必炸。
坑一:nginx 发了 PROXY 头,sing-box 没开接收
stream 配置长这样:
stream {
server {
listen 443;
proxy_pass 127.0.0.1:8443;
proxy_protocol on; # 向 sing-box 发 PROXY 头
}
}
nginx 这边发得挺欢,sing-box 那边没开接收。于是 sing-box 收到的第一个字节不是 VLESS 版本号,而是这么一行:
PROXY TCP4 8.8.8.8 1.2.3.4 54321 443
它拿这行当 VLESS 协议头去解析。UUID 对不上,版本也对不上,直接 parse VLESS header failed,断开。
表现是客户端握手秒失败,日志里全是解析错误。你第一时间想不到是 nginx 的锅——nginx -t 是过的,reload 也成功。它确实把流量"正常"转过去了,只是协议头被污染了。
解法是让 sing-box 开接收。旧版 sing-box 写在 inbound 上:
{
"type": "vless",
"listen": "127.0.0.1",
"listen_port": 8443,
"accept_proxy_protocol": true,
"users": []
}
这里还埋着个版本坑:sing-box 1.13 之后把这个字段从 JSON schema 里拿掉了(源码标了 Deprecated/removed)。照抄两三年前的教程,配置校验直接报 unknown field。升级完 sing-box 节点突然全挂,多半就是这种"配置没变、版本变了"。
坑二:反过来,sing-box 开了,nginx 没发
对称的坑。sing-box 端 accept_proxy_protocol: true,在那傻等 PROXY 头。nginx 端没开 proxy_protocol on,上来就是原始 TCP 流。
sing-box 等不到头,连接挂在那。客户端看到的是超时,不是秒断——这是和坑一最好用的区分点。
一句话记住:秒断,是 nginx 多发了头;超时,是 nginx 少发了头。
坑三:real_ip 信任了全世界
HTTP 场景(VLESS+WS)下最常见的手滑:
set_real_ip_from 0.0.0.0/0;
real_ip_header X-Forwarded-For;
0.0.0.0/0 的意思是"任何来源的 XFF 我都信"。等于跟全世界说:随便伪造,我照单全收。
于是日志里冒出 8.8.8.8 这样的"真实 IP",实际上是个连 TCP 都没握过手的假货。
更麻烦的是 sing-box 那边基于来源 IP 的规则:fallback 到伪装站、白名单放行、限流。这些要么被绕过,要么误伤自己人。
set_real_ip_from 只该写你前面那层代理的网段。
# 假设 nginx 前面只有一层内网反代
set_real_ip_from 10.0.0.0/8;
set_real_ip_from 127.0.0.1;
real_ip_header X-Forwarded-For;
real_ip_recursive on;
real_ip_recursive on 也值得单独说一句。默认 off 的时候,nginx 取 XFF 里最右边那个地址。多级代理下,最右边那个往往是倒数第二跳代理,不是真客户端。开了 recursive,nginx 从右往左找,取第一个不在信任列表里的地址——那才是真身。
坑四:裸 TCP 场景配 real_ip,纯属自我感动
VLESS 不走 WS、不走 gRPC,就是裸 TCP,尤其是配了 xtls-rprx-vision 流控的时候。这条链路走 stream 模块,real_ip 模块根本不在里面。
你要是这么写:
stream {
server {
listen 443;
proxy_pass 127.0.0.1:8443;
set_real_ip_from 0.0.0.0/0; # ❌ stream 里没有这个指令,报错
}
}
直接报错。stream 模块里没有 set_real_ip_from 这个指令。想给裸 TCP 传真实 IP,只有 PROXY protocol 一条路,别无分店。
能用的配置模板
裸 VLESS(TCP):nginx stream + PROXY protocol,两端对齐。
stream {
server {
listen 443;
proxy_pass 127.0.0.1:8443;
proxy_protocol on;
}
}
// sing-box(以你装的版本 schema 为准)
{
"type": "vless",
"listen": "127.0.0.1",
"listen_port": 8443,
"accept_proxy_protocol": true,
"users": [
{ "uuid": "你的-uuid", "flow": "xtls-rprx-vision" }
]
}
VLESS + WebSocket:走 HTTP 层,用 real_ip 模块,信任范围写死。
server {
listen 443 ssl;
server_name your.domain.com;
set_real_ip_from 127.0.0.1; # 只信任你自己的反代层
set_real_ip_from 10.0.0.0/8;
real_ip_header X-Forwarded-For;
real_ip_recursive on;
location /vless {
proxy_pass http://127.0.0.1:8443;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
怎么验证修好了
- 看 sing-box 日志:
journalctl -u sing-box -f。连接正常后能看到inbound/vless-in: connection from 8.8.8.8:54321这样的真实来源,不再是 127.0.0.1。 - 抓包确认 PROXY 头:
tcpdump -i lo -A port 8443 | head,开头应该有PROXY TCP4那一行。 - 试试能不能伪造 XFF:
curl -I -H "X-Forwarded-For: 8.8.8.8" https://your.domain/vless,然后翻 nginx access log。remote_addr 真变成 8.8.8.8,说明信任范围开太大了。
最后
给 sing-box 套 nginx 加真实 IP,就三件事:
- 先确认流量走 http 还是 stream。这决定你用 real_ip 还是 PROXY protocol,两条路不互通。
- PROXY protocol 必须两端对齐。nginx 开了 sing-box 就得开,只开一边,要么秒断要么超时。
set_real_ip_from只写可信网段。写0.0.0.0/0,等于把 IP 审计的信任权交给每个能发 HTTP 请求的人。
肯定有人要问:VLESS 本来就是加密的,日志里 IP 是真是假有什么关系?有关系。fallback 伪装、按 IP 的限流风控、被打时的溯源,全靠这一行 IP。IP 是假的,这些防线全是纸糊的。
踩完这坑我给自己立了条规矩:凡是动到代理协议传递的配置,先抓包验证,再 reload 上线。 三行配置毁一个节点的教训,一次就够了。
参考来源
- nginx ngx_http_realip_module 官方文档:http://nginx.org/en/docs/http/ngx_http_realip_module.html
- sing-box VLESS inbound 文档:https://sing-box.sagernet.org/configuration/inbound/vless/
- sing-box Listen Fields:https://sing-box.sagernet.org/configuration/shared/listen/
- sing-box 版本迁移说明(proxy_protocol 字段移除):https://sing-box.sagernet.org/migration/
✨ 本文由 DeepSeek 生成初稿,Claude 审核润色。