真实客户端 IP 透传——认知过程与最终结论
记录从误解到正确理解的完整推导过程,供日后参考。
一、最初的误解
以为只要在 Stream 层开启 proxy_protocol on,后端 Nginx 就能拿到客户端真实 IP。
客户端 → Cloudflare → Stream(proxy_protocol on) → Nginx
↑
以为这里能拿到客户端真实 IP
二、实际发现
查看 Nginx 访问日志:
$remote_addr = 2400:cb00:933:1000:2212:e43:9ce8:f204 ← Cloudflare 节点 IP
$http_x_forwarded_for = 89.150.132.191 ← 真实客户端 IP
$remote_addr 是 Cloudflare 的 IP,不是客户端真实 IP。
三、根本原因
proxy_protocol 携带的是与 Stream 建立 TCP 连接的那一方的 IP,也就是上一跳。
客户端(89.150.132.191)
↓
Cloudflare(2400:cb00:...) ← 和你的服务器建立 TCP 连接的是它
↓
Stream → proxy_protocol 携带的是 Cloudflare 的 IP
↓
后端 Nginx → $remote_addr = Cloudflare IP
Stream 只能看到和它直接建立 TCP 连接的那一方,看不穿 Cloudflare。
四、proxy_protocol 适用场景
| 链路 | proxy_protocol 是否有意义 |
|---|---|
| 客户端 直连 → Stream → Nginx | ✅ 有意义,携带的是真实客户端 IP |
| 客户端 → Cloudflare → Stream → Nginx | ❌ 没意义,携带的是 Cloudflare IP |
| 客户端 → 任何中间代理 → Stream → Nginx | ❌ 携带的是中间代理 IP |
结论:proxy_protocol 只对直连流量有意义。
五、各层代理传递真实 IP 的方式
每一层代理都有自己传递真实 IP 的机制:
| 代理层 | 传递方式 | 取值方式 |
|---|---|---|
| Cloudflare | CF-Connecting-IP Header | real_ip_header CF-Connecting-IP |
| 普通反向代理 | X-Forwarded-For Header | real_ip_header X-Forwarded-For |
| Stream / HAProxy | PROXY Protocol(TCP层) | real_ip_header proxy_protocol |
本质都一样:上游代理把真实 IP 塞进某个地方,下游负责读取并还原。
六、X-Forwarded-For 的工作原理
每经过一层代理就追加一个 IP,真实客户端 IP 永远在第一个:
客户端(1.1.1.1) → 代理A(2.2.2.2) → 代理B(3.3.3.3) → Nginx
X-Forwarded-For: 1.1.1.1, 2.2.2.2, 3.3.3.3
↑
取第一个 = 真实客户端 IP
但有伪造风险,客户端可以预填:
X-Forwarded-For: 8.8.8.8 ← 伪造
代理追加后变成 8.8.8.8, 1.1.1.1,取第一个就被骗了。
七、三个指令的组合拳
还原真实 IP 需要三个指令配合,缺一不可:
set_real_ip_from 127.0.0.1; # 谁是可信代理
real_ip_header X-Forwarded-For; # 去哪里取 IP
real_ip_recursive on; # 怎么取
各自职责
| 指令 | 职责 | 类比 |
|---|---|---|
set_real_ip_from | 定义可信代理列表 | 告诉我哪些 IP 是自己人 |
real_ip_header | 指定从哪里取 IP | 告诉我去哪个袋子里找 |
real_ip_recursive | 对取到的值怎么处理 | 告诉我怎么从袋子里找 |
缺少任何一个的后果
| 缺少 | 后果 |
|---|---|
缺 set_real_ip_from | 没有可信列表,无法判断哪个 IP 该剥离 |
缺 real_ip_header | 不知道去哪里取 IP |
缺 real_ip_recursive | 只取最右边一个,可能取到代理 IP 而不是真实 IP |
real_ip_recursive 的工作方式
从 real_ip_header 指定的来源从右往左检查:
X-Forwarded-For: 真实IP, 代理A, 代理B, 代理C
↑
从这里开始
代理C 在 set_real_ip_from 列表里 → 跳过
代理B 在 set_real_ip_from 列表里 → 跳过
代理A 在 set_real_ip_from 列表里 → 跳过
真实IP 不在列表里 → 停下,这就是真实 IP ✅
为什么从右往左:右边的 IP 是离你最近的代理加的,你能控制、可以信任;左边越来越远,最左边是客户端自己填的,可能被伪造。
real_ip_recursive 对不同来源的意义
real_ip_header 来源 | real_ip_recursive 是否有意义 |
|---|---|
X-Forwarded-For | ✅ 有意义,多个值需要递归剥离 |
CF-Connecting-IP | ❌ 没意义,单个值没得递归 |
proxy_protocol | ❌ 没意义,单个值没得递归 |
八、CF-Connecting-IP vs X-Forwarded-For
Cloudflare 转发请求时同时加上两个 Header:
CF-Connecting-IP: 89.150.132.191 ← 单个值,干净
X-Forwarded-For: 89.150.132.191, ... ← 可能有多个值
| Header | 特点 | 推荐场景 |
|---|---|---|
CF-Connecting-IP | 单个值,Cloudflare 专有,不可伪造 | Cloudflare 流量首选 |
X-Forwarded-For | 标准协议,需要递归剥离 | 通用场景 |
Cloudflare 场景推荐用 CF-Connecting-IP,更简单可靠。
九、$proxy_add_x_forwarded_for 与 real_ip_recursive 的关系
两者不冲突,职责不同,执行时机不同:
请求进来
↓
real_ip_recursive 处理,还原 $remote_addr = 真实IP ← 请求进来时
↓
Nginx 处理业务逻辑
↓
转发请求给后端
↓
$proxy_add_x_forwarded_for = 已有XFF + 还原后的 $remote_addr ← 请求出去时
还原后 $remote_addr 是真实 IP,$proxy_add_x_forwarded_for 追加的也就是真实 IP,两者配合传给后端的 XFF 链路更准确。
十、最终正确配置
两种流量叠加,Nginx 自动按来源选择:
# 直连流量:信任本机 Stream,从 PROXY Protocol 还原
set_real_ip_from 127.0.0.1;
real_ip_header proxy_protocol;
real_ip_recursive on;
# Cloudflare 流量:信任 CF 节点,从 CF-Connecting-IP 还原
set_real_ip_from 103.21.244.0/22;
set_real_ip_from 103.22.200.0/22;
set_real_ip_from 103.31.4.0/22;
set_real_ip_from 104.16.0.0/13;
set_real_ip_from 104.24.0.0/14;
set_real_ip_from 108.162.192.0/18;
set_real_ip_from 131.0.72.0/22;
set_real_ip_from 141.101.64.0/18;
set_real_ip_from 162.158.0.0/15;
set_real_ip_from 172.64.0.0/13;
set_real_ip_from 173.245.48.0/20;
set_real_ip_from 188.114.96.0/20;
set_real_ip_from 190.93.240.0/20;
set_real_ip_from 197.234.240.0/22;
set_real_ip_from 198.41.128.0/17;
set_real_ip_from 2400:cb00::/32;
set_real_ip_from 2606:4700::/32;
set_real_ip_from 2803:f800::/32;
set_real_ip_from 2405:b500::/32;
set_real_ip_from 2405:8100::/32;
set_real_ip_from 2a06:98c0::/29;
set_real_ip_from 2c0f:f248::/32;
real_ip_header CF-Connecting-IP;
还原后 $remote_addr 就是真实客户端 IP,所有依赖它的地方自动生效:
proxy_set_header X-Real-IP $remote_addr; # ✅ 真实 IP
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # ✅ 真实 IP 追加
limit_req_zone $remote_addr; # ✅ 按真实 IP 限流
access_log ... $remote_addr ... # ✅ 日志记录真实 IP
十一、验证方法
# 查看流量来源分布
awk '{print $1}' /etc/nginx/log/access.log | sort | uniq -c | sort -rn | head -20
如果 $remote_addr 全是 Cloudflare IP,说明 proxy_protocol 对真实 IP 没有贡献,应改用 CF-Connecting-IP。