工具大全
开发者工具作者:Coocon2026年8月2日175 次阅读约 25 分钟阅读

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-ForX-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";
    }
}

怎么验证修好了

  1. 看 sing-box 日志journalctl -u sing-box -f。连接正常后能看到 inbound/vless-in: connection from 8.8.8.8:54321 这样的真实来源,不再是 127.0.0.1。
  2. 抓包确认 PROXY 头tcpdump -i lo -A port 8443 | head,开头应该有 PROXY TCP4 那一行。
  3. 试试能不能伪造 XFFcurl -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,就三件事:

  1. 先确认流量走 http 还是 stream。这决定你用 real_ip 还是 PROXY protocol,两条路不互通。
  2. PROXY protocol 必须两端对齐。nginx 开了 sing-box 就得开,只开一边,要么秒断要么超时。
  3. set_real_ip_from 只写可信网段。写 0.0.0.0/0,等于把 IP 审计的信任权交给每个能发 HTTP 请求的人。

肯定有人要问:VLESS 本来就是加密的,日志里 IP 是真是假有什么关系?有关系。fallback 伪装、按 IP 的限流风控、被打时的溯源,全靠这一行 IP。IP 是假的,这些防线全是纸糊的。

踩完这坑我给自己立了条规矩:凡是动到代理协议传递的配置,先抓包验证,再 reload 上线。 三行配置毁一个节点的教训,一次就够了。


参考来源

✨ 本文由 DeepSeek 生成初稿,Claude 审核润色。

相关文章

码农早餐 · 2026-09-16

今日头菜:eBPF 安全 agent 的开销,一个 inode 缓存砍掉 90%。另有 4 条快讯:Cloudflare 把源站握手猜错率从 52% 压到 3.7%;Qwen3 语音双模型开源:63ms 首字延迟,价格是 ElevenLabs 的五分之一;等。

daily-intel2026年9月16日12 min
63

码农早餐 · 2026-09-15

今日头菜:35KB 提示词迁到自托管 Ollama:能跑,但 3 分钟就开始没油。另有 4 条快讯:PyO3 实战:JSON 转换可以比解析本身还慢;RX 9060 XT 跑通 CUDA:ZLUDA + HIP SDK 6.4 的一份可复现清单;等。

daily-intel2026年9月15日12 min
70

码农早餐 · 2026-09-14

今日头菜:Zoom 7.1.5 会主动读走你 X11 剪贴板里的每一次复制。另有 7 条快讯:一天 2 美元 vs 一个月 200 美元:开发者用脚投票选默认模型;Dario 呼吁「放缓前沿」,被将了一军:先开源权重;等。

daily-intel2026年9月14日14 min
88

上周教你手写 git worktree,这周发现有人做成了一条命令:worktrunk 实测

上一篇讲用 git worktree 替代 stash,留了个尾巴:node_modules 得重装一遍。worktrunk 是一个 7.4k 星的 Rust 命令行工具,把 worktree 的建、切、列、删、合做成了 wt 一条命令,还顺手把 node_modules 的问题用 APFS 克隆解决了:56,233 个文件 1.7 GiB,3 秒拷完,磁盘一字节不多占。本文在一个真实仓库里跑了一遍,讲清哪几步真被省掉、哪个默认行为会把 .env 一起拷走、哪些功能我不会开。

工作流ai-agent+5
developer2026年9月13日6 min
65