适用场景
业务服务部署在负载均衡、CDN、Kubernetes Ingress 或四层代理之后。应用日志中的 remote_addr 全是代理的内网地址,限流、风控和审计都无法识别真实访问者;更危险的是,直接信任请求携带的 X-Forwarded-For,会让攻击者伪造来源 IP 绕过白名单。
本文以“公网负载均衡 → Nginx → 应用”的链路为例,说明如何定位 IP 在哪一跳丢失,并给出只信任受控代理的 Nginx 配置。
现象描述
常见现象包括:
- Nginx access log 中
$remote_addr全为10.x.x.x或 Pod IP; - 应用侧按 IP 限流失效,多个用户被误判为同一个来源;
- 安全日志无法关联攻击请求的公网地址;
- 手工发送
X-Forwarded-For: 1.2.3.4后,应用日志竟显示为1.2.3.4。
最后一种情况不是“配置成功”,而是信任边界错误,应优先修复。
先画清楚 IP 链路
每一跳可能新增或覆盖不同字段:
客户端 203.0.113.24
-> 负载均衡 10.20.0.10(写入 X-Forwarded-For)
-> Nginx 10.20.1.15
-> 应用
到达 Nginx 时,TCP 对端地址是负载均衡的 10.20.0.10,所以 $remote_addr 默认也应是它。真实来源只能来自受信任代理转发的 HTTP 头,或四层代理提供的 PROXY protocol;二者不能混用或凭感觉配置。
排查步骤
1. 临时记录原始地址与转发头
在 Nginx 的 http 块内增加一个仅用于排查的日志格式,并为目标站点启用:
log_format realip_debug '$time_local request="$request" '
'peer=$remote_addr realip=$realip_remote_addr '
'xff="$http_x_forwarded_for" '
'x_real_ip="$http_x_real_ip"';
server {
access_log /var/log/nginx/realip-debug.log realip_debug;
}
重新加载后观察:
sudo nginx -t && sudo systemctl reload nginx
sudo tail -f /var/log/nginx/realip-debug.log
peer 是 realip 模块处理后的客户端地址;realip 保留处理前的 TCP 对端地址。若 xff 为空,问题在上游代理;若 xff 有值但 peer 未变化,Nginx 尚未启用 realip 或受信任网段不匹配。
2. 从代理节点验证头是否被正确传递
在负载均衡之后的 Nginx 上抓取一条请求,同时核对代理配置。对于 HTTP 七层代理,通常应追加而不是覆盖链路:
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
$proxy_add_x_forwarded_for 会把当前连接对端追加到已有链路;写成固定值或只使用 $remote_addr,会丢失前面代理留下的记录。
3. 确认入口是否使用 PROXY protocol
若四层负载均衡启用了 PROXY protocol,Nginx 监听端口必须显式匹配:
server {
listen 443 ssl proxy_protocol;
real_ip_header proxy_protocol;
set_real_ip_from 10.20.0.0/16;
}
只在负载均衡确认会发送 PROXY protocol 时使用 proxy_protocol。一端开启、另一端未开启,通常会直接造成 TLS 或 HTTP 请求失败。HTTP 头方式则使用下一节的 X-Forwarded-For 配置,不要在同一个入口混杂两套来源。
推荐配置:只信任受控代理
以下示例适用于负载均衡以 HTTP 头转发来源地址,且负载均衡网段为 10.20.0.0/16、集群 Ingress 网段为 10.30.0.0/16:
# 放在 http 块;网段必须按实际出口 IP 收紧。
set_real_ip_from 10.20.0.0/16;
set_real_ip_from 10.30.0.0/16;
real_ip_header X-Forwarded-For;
real_ip_recursive on;
log_format main_with_realip '$remote_addr peer=$realip_remote_addr '
'xff="$http_x_forwarded_for" '
'request="$request" status=$status';
server {
listen 80;
access_log /var/log/nginx/access.log main_with_realip;
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://application_upstream;
}
}
set_real_ip_from 是安全边界:只有 TCP 对端属于这些网段,Nginx 才会读取转发头。real_ip_recursive on 会在 X-Forwarded-For 链中从右向左跳过受信任代理,取第一个不受信任地址,适合多层自有代理。不要填写 0.0.0.0/0,也不要把办公网、Pod 全网段不加区分地列入信任范围。
可复现的验证方法
先从一台不在受信任网段的测试机发伪造头:
curl -sS -o /dev/null -D - \
-H 'X-Forwarded-For: 198.51.100.77' \
https://example.com/healthz
此时日志中的客户端地址应仍是测试机的实际出口地址,而不是 198.51.100.77。随后通过真实负载均衡访问同一地址,日志应显示客户端公网 IP,peer 则应保留负载均衡地址。
若需要验证应用收到的字段,可在受控环境临时返回:
location = /__debug/realip {
default_type text/plain;
return 200 "client=$remote_addr peer=$realip_remote_addr xff=$http_x_forwarded_for\n";
}
验证完成后删除该调试接口,避免暴露内部拓扑。
常见错误与修复
| 错误做法 | 风险或症状 | 修复方式 |
|---|---|---|
未配置 set_real_ip_from 却读取头 |
配置不生效或误以为应用应自行处理 | 在 Nginx 定义精确的代理出口网段 |
信任 0.0.0.0/0 |
任意客户端伪造 IP 绕过审计和限流 | 仅允许 LB、CDN 或 Ingress 的已知地址段 |
real_ip_recursive off |
多层代理时取到中间代理而非用户 | 链路均受控时启用 on 并核验链路 |
把 X-Real-IP 当多跳标准链路 |
只能保存单个地址且常被覆盖 | 使用 X-Forwarded-For,保留原始头用于审计 |
| 对 PROXY protocol 端口发送普通 HTTP | 请求解析失败 | 统一负载均衡与 listen ... proxy_protocol 的开关 |
预防措施
- 把负载均衡、CDN 和 Ingress 的出口 CIDR 纳入配置管理,并在变更时评审;云厂商 IP 段变化应有更新流程。
- 在网关日志中同时保留处理后的客户端 IP、原始对端 IP 与原始转发头,便于追溯伪造和配置回归。
- 对 IP 白名单、登录风控、限流等安全能力,在预发布环境执行“直连伪造头应失败、经受信任代理应成功”的回归测试。
- 在 WAF 或边缘层清理来自公网客户端的伪造
X-Forwarded-For,由第一跳受控代理重新写入。
总结
反向代理后的真实 IP 问题,本质是来源证明的信任边界问题。先通过日志区分 TCP 对端、转发头和最终生效地址,再根据 HTTP 头或 PROXY protocol 选择唯一的传递方式。只让受控代理拥有“声明客户端地址”的权限,才能同时恢复可观测性并避免 IP 伪造。
Discussion
评论