反向代理与真实 IP 的传递
上了反向代理或 CDN 后,应用里 $remote_addr 变成了代理的 IP。日志、限流、封禁全部失真。正确取回真实 IP 需要理解转发头的语义。
X-Forwarded-For 是追加,不是覆盖
X-Forwarded-For: 客户端IP, 代理1, 代理2
每一跳把自己的来源追加到链尾。这带来一个关键问题:客户端可以伪造第一段。攻击者直接发一个带 X-Forwarded-For: 1.2.3.4 的请求,代理会原样追加,应用若取第一段,拿到的是伪造值。
正确姿势:从右往左数
信任链是固定的:你知道有几个可信代理,就从右往左数第 N 个。例如前置 CDN + 本机 Nginx,则真实 IP 是倒数第二个:
# Nginx:信任 CDN 网段,realip 模块自动改写 $remote_addr
set_real_ip_from 173.245.48.0/20; # CDN 网段
real_ip_header X-Forwarded-For;
real_ip_recursive on;
应用层直接取 $remote_addr,不需要自己解析头。永远不要无条件信任 XFF 第一段——它等价于把 IP 来源控制权交给客户端。
传递头的选择
X-Forwarded-For:事实标准,链式;X-Real-IP:Nginx 常用的单值头,由最外层代理设置;- 标准化方案是
Forwarded头(RFC 7239),语法严谨但生态支持一般。
location / {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://backend;
}
框架层
多数框架有「受信任代理」配置:PHP 下 $ _SERVER['REMOTE_ADDR'] 与 HTTP_X_FORWARDED_FOR 的取舍、Express 的 trust proxy、Laravel 的 trustedproxies 包。配置的网段必须与实际部署一致,配宽松了等于信任任意客户端。
排查方法
在每层记录收到的头与连接来源,对同一请求串起来看:
log_format trace '$remote_addr | XFF: $http_x_forwarded_for | $http_x_real_ip';
大多数「限流把所有用户算成同一个 IP」「封禁封不住」的问题,答案都在这条日志里。