在云原生里,把真实客户端 IP「请」回来:原理、配置与避坑
在云原生架构下,你的应用通常不是直接面对公网用户的:WAF、CDN、SLB/ELB、Ingress/Gateway 等安全与流量治理组件会先接住连接。于是到了 Nginx 或应用服务这层,$remote_addr 往往变成了上游代理的出口 IP,而非用户的真实公网 IP。
本文从原理讲到实操,带你把真实客户端 IP “请”回来,并尽量做到安全、稳妥、可审计。
背景:为什么 $remote_addr 常常是“代理 IP”?
从 TCP 视角看,Nginx 建立的连接对端就是 WAF/SLB/Ingress 的出口地址。对 Nginx 而言,它只看得到和自己握手的那台机器;至于最初的用户请求在前面经历了几层转发,这些信息不会自动体现在 $remote_addr 上。
解决思路:把“用户真实 IP”带到七层(HTTP 头)或者带到四层(PROXY protocol),再由 Nginx/应用可信地取回并替换。
真实 IP 被放在哪里?
几乎所有上游代理都会把原始客户端 IP写进请求的某个位置,常见有:
X-Forwarded-For:<client-ip>, <proxy1-ip>, <proxy2-ip>, ...规范是靠右追加;最左侧通常是原始客户端。X-Real-IP:有些设备会直接写入客户端 IP。Forwarded(RFC 7239):Forwarded: for=<client-ip>; proto=https; by=<proxy-ip>- 厂商私有头:如 Cloudflare 的
CF-Connecting-IP、部分厂商的True-Client-IP(多为高配/企业版)。
关键点:这些头在语义上可靠,但在来源上不可靠。用户自己就能伪造 X-Forwarded-For,因此必须结合“可信代理名单”做校验。
在 Nginx 里“安全地”拿回真实 IP(HTTP 场景)
直接日志里打印
$http_x_forwarded_for是不安全的——任何人都能伪造这个头。
正确做法:启用 ngx_http_realip_module,只信任自家代理的出口网段,再指明从哪个头取 IP,并递归解析多级代理。
# http {} 块里(尽量放在 limit_req 等模块之前)
# 1) 声明可信上游(只写你自家的出口网段 / 云厂商的固定段)
set_real_ip_from 10.0.0.0/16;
set_real_ip_from 192.168.0.0/24;
# IPv6 也要加(示例)
set_real_ip_from 2406:da1:abcd::/48;
# 2) 指定用哪个头带回真实 IP(常见选项见下文云厂商差异)
real_ip_header X-Forwarded-For;
# 3) 递归解析多级代理(从右到左回溯可信链,取第一个“不可信”左侧的那个)
real_ip_recursive on;
# 4) 日志建议同时保留 remote_addr(已还原)与原始 XFF 便于排障
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'status=$status bytes=$body_bytes_sent '
'xff="$http_x_forwarded_for" ua="$http_user_agent"';
access_log /var/log/nginx/access.log main;
real_ip_recursive on;的行为要点:Nginx 会从X-Forwarded-For最右侧开始向左检查,跳过你标记为可信的代理地址;遇到第一个不在可信列表的地址时,把它视为真实客户端 IP。如果整串都可信,则取最左侧的地址。
云厂商 / CDN 的差异化建议
| 场景 | 推荐 real_ip_header | 可信网段(示例) | 备注 |
|---|---|---|---|
| Cloudflare 反向代理 | CF-Connecting-IP(优选)或 X-Forwarded-For | Cloudflare 官方 CIDR 列表 | CF-Connecting-IP 专门指向原始客户端 IP;请定期同步官方网段。 |
| AWS ALB/ELB(HTTP/HTTPS) | X-Forwarded-For | 自家 ALB/ELB 的出口网段或安全组来源段 | ALB 遵循 XFF 追加规则,结合 real_ip_recursive on 使用。 |
| GCP Load Balancer(HTTP(S)) | X-Forwarded-For | GFE 出口段(Google 前端) | 同理,维护可信列表。 |
| Azure Front Door / App GW | X-Forwarded-For | 对应出口地址段 | 同理。 |
| 私有 Nginx/Haproxy 反代 | X-Forwarded-For 或 X-Real-IP | 自家出口网段 | 内部统一规范最重要。 |
在 Kubernetes 中(如 NGINX Ingress、Kong、Envoy Gateway),通常网关层已经做过一次 real IP 还原;若你的应用 Pod 内还有 Nginx,再做一次需要确保信任链一致,避免“重复信任”造成错误。
非 HTTP/HTTPS:WebSocket、TLS 直透、纯 TCP 怎么办?
HTTP 头拿不到时,应使用 PROXY protocol(推荐)或网络层透明代理(复杂)。
方案一:PROXY protocol(强烈推荐)
上游设备在 TCP 握手时前置一段明文(v1)或二进制(v2)元数据,包含原始源/目的地址与端口。Nginx 在 stream 或 http 监听时打开解析即可。
HTTPS/TCP(stream)示例:
stream {
upstream backend_tls {
server 10.0.1.10:443;
}
server {
listen 443 proxy_protocol; # 监听端口启用 PROXY 协议
set_real_ip_from 10.0.0.0/16;# 只信任上游代理出口网段
proxy_protocol on; # 向下游继续透传(可选)
# 对于 stream 场景,使用 proxy_protocol 的地址变量
# $proxy_protocol_addr 为真实客户端 IP
proxy_pass backend_tls;
}
}
HTTP(listen 处启用 PROXY)示例:
server {
listen 443 ssl proxy_protocol;
set_real_ip_from 10.0.0.0/16;
real_ip_header proxy_protocol; # 指明从 PROXY 协议读取真实 IP
# ...
}
公有云里,AWS NLB 支持 PROXY protocol v2(需在 Target Group 打开);自建 HAProxy/Envoy 也常见。优先用 v2(二进制、字段更丰富)。
方案二:透明代理 / TPROXY(不推荐)
在网络层保留源地址,需要路由与内核配置(如 iptables TPROXY、rp_filter、策略路由等)。运维复杂且可移植性差,除特殊需求外不建议在公有云采用。
把真实 IP 继续传给后端应用
Nginx 侧还原后,建议统一向后端透传(避免多处重复逻辑):
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # 在现有 XFF 上追加
proxy_set_header X-Real-IP $remote_addr; # 这里已是“还原后”的 IP
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port $server_port;
应用侧可优先读
X-Real-IP(若你保证来源可信),或按统一库/中间件解析 XFF(谨慎处理信任链)。
易错点与最佳实践清单
常见误区
- 忘了配可信网段:Nginx 会误信任用户自造的 XFF,攻击者可把自己伪装成任意 IP。
- 没开递归解析:多级代理时只取到“中间层”IP。
- 只记录真实 IP,不留 XFF:丢失排障线索,无法还原路径。
- 后端没透传:应用拿不到真实 IP。
- 顺序不当:
limit_req_zone等限流/防刷基于$binary_remote_addr的配置要在还原真实 IP 之后生效。
最佳实践
- 只信任自家代理/WAF/SLB/CDN 的出口段(含 IPv6),并定期同步厂商网段。
real_ip_header X-Forwarded-For;+real_ip_recursive on;(或厂商专用头如CF-Connecting-IP)。- 日志同时打印
$remote_addr(已还原)与原始X-Forwarded-For。 - 向后端统一透传(
X-Real-IP/X-Forwarded-*)。 - 四层场景优先用 PROXY protocol v2;K8s/公有云优先启用负载均衡的原生支持。
- 在
http {}里放置 realip 配置,并且在 限流/防火墙规则定义之前,使其基于真实 IP 生效。
验证与排障:怎么确定“拿对了”?
1)模拟伪造 XFF,验证“拒信任”是否生效
假设你的服务前面有 ALB/Ingress,会自动加上自己的 XFF;我们再人为伪造一段靠左的地址,确保 Nginx 不会采信它。
curl -H 'X-Forwarded-For: 1.2.3.4' -H 'User-Agent: test' https://your.domain.com/test
# 查看 Nginx 日志中 $remote_addr 与 xff 字段,确认 remote_addr 不是 1.2.3.4
2)逐层对照
从 CDN/WAF 控制台抓取一次请求(或开启临时调试头),核对:
用户公网 IP → CDN/WAF 日志 → 负载均衡访问日志 → Nginx access.log 的 xff 与最终 remote_addr。
3)IPv6 检查
用可控 IPv6 客户端(或在线 IPv6 测试)访问,确认 set_real_ip_from 已含 IPv6 段,日志里能还原 remote_addr 为 IPv6。
4)限流/风控验证
对固定 IP 做限流(如 limit_req),确认规则命中真实 IP(而不是代理 IP)。
进阶话题
- 标准
Forwarded头(RFC 7239):若你的链路支持,优先在网关层标准化生成;应用可有更丰富的上下文(proto、host、by等)。 - GeoIP/ACL 与 realip 的先后关系:需要按真实 IP 决策的模块(GeoIP、WAF、限速、访问控制),都应在 realip 还原之后取值。
- 多网关/多 CDN 叠加:信任链可能包含多段 CIDR,务必最小化可信范围(只放你直接上游的出口段),避免把整条公网都“可信化”。
常用配置片段速查
HTTP(最常见):
http {
set_real_ip_from 10.0.0.0/16;
set_real_ip_from 192.168.0.0/24;
real_ip_header X-Forwarded-For;
real_ip_recursive on;
log_format main '$remote_addr "$request" status=$status xff="$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
# ... 其他全局/限流配置(此时已基于真实 IP)
}
Cloudflare(优先用专用头):
http {
# 按官方文档同步所有 Cloudflare IPv4/IPv6 段
set_real_ip_from 173.245.48.0/20;
# ... 省略其余段
set_real_ip_from 2400:cb00::/32;
real_ip_header CF-Connecting-IP; # 直接取原始客户端 IP
real_ip_recursive on;
}
HTTPS/TCP(stream + PROXY protocol):
stream {
set_real_ip_from 10.0.0.0/16;
server {
listen 443 proxy_protocol;
# $proxy_protocol_addr 即为真实客户端 IP,可写入自定义日志或传下游
proxy_pass 10.0.1.10:443;
}
}
结论与清单
- 为什么看不到真实 IP?因为 TCP 对端是 WAF/SLB/网关,
$remote_addr默认就是它。 - HTTP 场景:用
X-Forwarded-For(或厂商专用头)+realip模块,配可信网段 + 开递归。 - 非 HTTP 场景:用 PROXY protocol(优先 v2),在
listen与real_ip_header proxy_protocol上配齐。 - 最佳实践:
1)配置可信网段(含 IPv6,最小化范围);
2)递归解析多级代理;
3)日志同时保留还原后的
$remote_addr与原始XFF; 4)把真实 IP 统一透传给后端; 5)把限流/风控/ACL 放在还原之后。
做好这些后,无论是日志分析、风控策略还是安全审计,你都能基于准确的用户源 IP做判断与决策。
附录:常见 cdn 与真实 IP 头
| 序号 | Header 名称 | 说明/来源 |
|---|---|---|
| 1 | EO-Client-IP | Edgeone |
| 2 | CF-Connecting-IP | Cloudflare |
| 3 | True-Client-IP | Akamai / Cloudflare 企业版 |
| 4 | Fastly-Client-IP | Fastly |
| 5 | ali-real-client-ip | 阿里云 ESA |
| 6 | X-Azure-ClientIP | Azure Front Door / AppGW |
| 7 | X-Azure-SocketIP | Azure (socket 级) |
| 8 | Forwarded | RFC 7239 标准 |
| 9 | X-Forwarded-For | 行业通用,逐级追加 |
| 10 | X-Original-Forwarded-For | AWS ALB / ELB |
| 11 | X-Real-IP | Nginx 反代 |
| 12 | X-Client-IP | Apache mod_remoteip |
| 13 | X-Cluster-Client-IP | 集群环境 |
| 14 | X-Varnish-Client-IP | Varnish |
| 15 | X-Forwarded | 旧式头 |
| 16 | Forwarded-For | 非标准 |
| 17 | HTTP_X_FORWARDED_FOR | PHP / CGI 环境常见 |
| 18 | HTTP_CLIENT_IP | 一些应用/框架会使用 |
| 19 | WL-Proxy-Client-IP | WebLogic |
| 20 | Proxy-Client-IP | 通用代理(常见于老系统) |