必须启用real_ip模块并配置可信源,用set_real_ip_from和real_ip_header还原$remote_addr后,再proxy_set_header X-Real-IP $remote_addr透传;否则缓存节点回源时$remote_addr仅为自身IP。

要让缓存回源(比如 CDN 或 Nginx 自身 proxy_cache)场景下,后端服务仍能拿到原始客户端真实 IP,光写 proxy_set_header X-Real-IP $remote_addr; 是不够的——因为缓存节点可能先发起回源请求,此时 $remote_addr 变成了缓存服务器自身的 IP,而非用户真实 IP。
关键前提:区分“接入层”和“缓存层”角色
真实 IP 的源头只在最外层可信代理(如 CDN 边缘节点、WAF、LB)与用户直连时才可准确获取。后续每一层都应只传递、不伪造,并明确信任链。
- 如果 Nginx 是缓存节点(即启用了
proxy_cache),它本身不直接面对用户,$remote_addr就是上游 CDN 的 IP,不是用户 IP - 此时必须依赖上游已传入的
X-Forwarded-For或X-Real-IP,并用real_ip模块解包还原$remote_addr - 还原后,再用
proxy_set_header X-Real-IP $remote_addr;向后端传递,才真正有效
必须启用 real_ip 模块并限定可信来源
仅靠 proxy_set_header 转发头,后端拿到的仍是缓存节点自己的 IP;必须让 Nginx 先“相信”上游头里写的 IP 是真的,并把 $remote_addr 替换为那个值。
- 在
http或server块中配置可信代理段(例如阿里云 CDN 回源网段、Cloudflare IP 列表):set_real_ip_from 103.21.244.0/22;<br>set_real_ip_from 173.245.48.0/20;<br>real_ip_header X-Forwarded-For;<br>real_ip_recursive on;
-
real_ip_recursive on表示从X-Forwarded-For最右往左找第一个非私有、非保留 IP,通常就是用户真实 IP - 启用后,
$remote_addr和$realip_remote_addr都会变为解包后的用户 IP,后续proxy_set_header X-Real-IP $remote_addr才有意义
回源请求中正确透传,避免覆盖或丢失
缓存节点向后端发起回源请求时,需确保原始头被继承或补全,而不是丢弃或误写:
- 不要写
proxy_set_header X-Forwarded-For $remote_addr;—— 这会抹掉上游已带的完整链路 - 应使用:
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
它会在原有X-Forwarded-For值末尾追加当前$remote_addr(即已解包的用户 IP),保持链路可追溯 - 同时设置:
proxy_set_header X-Real-IP $remote_addr;—— 此时$remote_addr已是真实 IP,可被后端优先读取 - 若后端只认
X-Real-IP,这个头就足够;若需兼容多层日志审计,X-Forwarded-For更可靠
后端必须主动读取 Header,不能依赖 socket 地址
即使 Nginx 完美透传,后端框架默认仍调用 request.getRemoteAddr()(Java)、req.ip(Express)等,返回的是 Nginx 连接 IP,不是真实 IP。
- Java Spring:需配合
javax.servlet.http.HttpServletRequest.getRemoteAddr()改为读request.getHeader("X-Real-IP")或解析X-Forwarded-For最左 IP - Node.js Express:启用
app.set('trust proxy', true),然后用req.ip或req.ips,它会自动识别X-Forwarded-For - Python Flask:需手动检查
request.headers.get('X-Real-IP')或使用request.access_route
不复杂但容易忽略:真实 IP 不是“传过去就行”,而是“可信地解出来,再干净地传下去”。缓存回源时,Nginx 必须先当好“解包者”,再当“转发者”。











