仅配置 proxy_set_header 不足以让后端获取真实 ip,必须同时满足:1. 用 real_ip_module(set_real_ip_from、real_ip_header、real_ip_recursive)使 $remote_addr 变为真实 ip;2. 在 proxy_pass 前正确设置 proxy_set_header x-real-ip $remote_addr 和 x-forwarded-for $proxy_add_x_forwarded_for;3. 后端代码显式读取对应 header 字段。

只写 proxy_set_header 不足以让后端拿到真实 IP。必须先让 Nginx “认出”真实客户端 IP,再用它设置头字段,最后后端还得主动读取——三者缺一不可。
确保 $remote_addr 是真实 IP
Nginx 默认的 $remote_addr 是直接和它建立 TCP 连接的上一级节点 IP。如果前面有 CDN、云负载均衡(如阿里云 SLB)、WAF 或其他反向代理,这个值就变成了代理出口 IP,不是用户真实出口 IP。
要还原真实 IP,需配合 real_ip_module 指令:
-
set_real_ip_from明确声明可信代理的 IP 或网段(例如:set_real_ip_from 203.205.128.0/17;对应阿里云 SLB 回源段) -
real_ip_header指定上游传来的哪个头含真实 IP(常用X-Forwarded-For或X-Real-IP) -
real_ip_recursive on;启用递归解析:从X-Forwarded-For的逗号分隔列表(如203.0.113.1, 198.51.100.2, 192.168.1.100)中,从右往左剔除所有已声明的可信代理 IP,最终把最左边那个非私有、非代理的 IP 设为新的$remote_addr
正确配置 proxy_set_header 透传
上述步骤生效后,$remote_addr 才真正代表用户真实 IP。此时再用它设置请求头才可靠,且必须放在 proxy_pass 行之前:
-
proxy_set_header X-Real-IP $remote_addr;—— 单值清晰,后端可直取,不建议用$http_x_real_ip等原始头,防伪造 -
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;—— 自动继承上游已有值并在末尾追加当前层真实 IP,保留完整链路(如203.0.113.1, 198.51.100.2),后端取第一个即可 - 切忌写成
proxy_set_header X-Forwarded-For $remote_addr;,否则会覆盖上游原始值,丢失最左真实 IP
后端必须显式读取 header
Nginx 不会改写后端的 REMOTE_ADDR 或 req.connection.remoteAddress。后端框架默认返回的仍是连接发起方地址(即 Nginx 的 IP),不是 header 里的值。
需在代码中手动提取:
- PHP:
$_SERVER['HTTP_X_REAL_IP'] - Python Flask:
request.headers.get('X-Real-IP')或request.headers.get('X-Forwarded-For', '').split(',')[0].strip() - Node.js Express:
req.headers['x-real-ip']或req.headers['x-forwarded-for']?.split(',')[0].trim() - Java Servlet:
request.getHeader("X-Real-IP"),而非getRemoteAddr()
让 access_log 也记录真实 IP
若希望 Nginx 日志中直接显示用户真实 IP(而非代理或内网地址),复用上面的 set_real_ip_from 和 real_ip_header 配置即可。启用后,日志变量 $remote_addr 就自动变为真实 IP,无需额外从 header 解析。
示例日志格式:
log_format main '$remote_addr - $remote_user [$time_local] "$request" ''$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';











