Nginx作为反向代理时,后端服务往往需要知道发起请求的原始客户端IP,而不是代理服务器自身的IP。为了实现这一目标,开发者通常会接触到remote_addr和proxy_add_x_forwarded_for这两个概念。然而很多人在配置时并没有真正理解它们的区别,导致日志记录混乱、访问控制失效甚至安全漏洞。本文从数据来源、行为逻辑和实际配置三个层面展开分析,帮助你在多层代理架构中准确传递客户端地址。

一、基本概念:remote_addr与X-Forwarded-For
在Nginx中,$remote_addr是一个内建变量,它保存的是与Nginx直接建立TCP连接的对端IP地址。如果客户端直接访问Nginx,那么$remote_addr就是客户端的公网IP;但如果中间经过了其他反向代理或负载均衡器,这个值就变成了上一跳代理的IP,而不再是原始用户IP。例如,当用户请求先到达一层CDN,再由CDN回源到Nginx时,Nginx看到的$remote_addr就是CDN节点的IP。
另一方面,X-Forwarded-For(简称XFF)是一个非标准的HTTP请求头,通常由代理服务器在转发请求时添加,用来记录请求经过的每一层代理看到的客户端地址。其格式为逗号分隔的IP列表,最左边是原始客户端IP,后续依次是每一级代理追加的地址。例如:X-Forwarded-For: 203.0.113.5, 70.41.3.18, 150.172.238.178。这个头本身是可伪造的,任何客户端都可以在请求中自带一个XFF头,因此仅依赖它获取真实IP存在风险。
从定义就能看出,$remote_addr是可靠的、无法被客户端伪造的,因为它取自TCP握手信息;而XFF则是纯粹的应用层数据,只能作为参考。理解这一点是后续配置决策的基础。
二、proxy_add_x_forwarded_for的实际行为
Nginx的proxy_set_header指令常用来修改转发给上游服务器的请求头。其中$proxy_add_x_forwarded_for是一个特殊变量,它的值等于请求中已有的$http_x_forwarded_for(即客户端传来的XFF头内容)加上当前的$remote_addr,中间用英文逗号和空格连接。如果客户端没有带XFF头,那么该变量的值就等于$remote_addr。
location / {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://backend;
}举例来说,假设客户端IP为1.2.3.4,直接请求Nginx(Nginx的remote_addr也是1.2.3.4),客户端没有伪造XFF头,那么Nginx转发给后端时,设置的头就是X-Forwarded-For: 1.2.3.4。如果该请求之前经过了一层代理5.6.7.8,且代理已经加上了X-Forwarded-For: 1.2.3.4,那么Nginx的$remote_addr是5.6.7.8,此时$proxy_add_x_forwarded_for的值就是1.2.3.4, 5.6.7.8。这也是最常见的“追加”语义。
需要注意,如果客户端恶意发送了伪造的XFF头,比如X-Forwarded-For: 9.9.9.9,那么Nginx在追加时会把伪造值保留下来,变成9.9.9.9, 1.2.3.4。后端如果天真地取第一个IP作为客户端地址,就会被欺骗。这就是XFF不可信的原因,也是为什么很多安全实践要求丢弃客户端自带的XFF头或者只信任受控代理。
三、多层代理下的真实IP获取策略
在实际生产环境中,请求链路往往包含多个代理层,例如用户 -> CDN -> 负载均衡器 -> Nginx -> 应用服务。每一层都可能追加或修改XFF头,但最终到达应用时,后端需要还原出最原始的客户端IP。如果只是机械地在每一层使用proxy_add_x_forwarded_for,最终得到的列表会很长,并且可能包含被伪造的条目。
更稳妥的做法是配合Nginx的real_ip模块,将可信代理的IP从XFF头中剥离,然后将真实的客户端地址覆盖到$remote_addr变量上。例如,假设我们的Nginx前面只有一层受信任的CDN,CDN的IP段为10.10.0.0/16,我们可以这样配置:
set_real_ip_from 10.10.0.0/16; real_ip_header X-Forwarded-For; real_ip_recursive on;
set_real_ip_from告诉Nginx哪些来源IP是可信代理,real_ip_header指定用哪个头来提取原始IP,real_ip_recursive on则允许递归搜索,跳过所有可信代理地址,直到找到第一个不可信地址作为真实IP。经过这样的处理,$remote_addr就会被替换成用户真实IP,而后端可以继续安全地使用$remote_addr。
需要注意的是,real_ip模块修改$remote_addr之后,如果再使用$proxy_add_x_forwarded_for,追加的也是修改后的IP,而不是原始的TCP对端。因此在多层代理场景下,配置顺序和信任列表需要仔细设计。通常推荐只在最外层的可信代理上使用real_ip模块,内部各层直接透传已经修正过的$remote_addr。
四、常见误区与安全注意事项
第一个常见误区是认为$remote_addr永远代表真实客户端IP。这个错误在引入了反向代理后立即暴露,因为Nginx与后端之间的连接对端变成了代理服务器。很多初学者在日志格式中直接使用$remote_addr,结果日志里全是127.0.0.1或内网IP。要解决这个问题,要么在日志中使用$http_x_forwarded_for的第一个值,要么在受控环境中使用real_ip模块重写$remote_addr。
第二个误区是完全信任XFF头的内容并用作鉴权依据。攻击者可以轻易通过curl等工具添加任意的X-Forwarded-For头,如果应用层根据这个头做IP白名单或限流,可能被绕过。正确的做法是:只信任由可信代理设置或追加的XFF,或者在Nginx层丢弃客户端原始XFF并重新生成。例如:
proxy_set_header X-Forwarded-For $remote_addr;
上述配置完全覆盖客户端传入的XFF,只用当前连接的直连IP(也就是可信代理的IP)作为XFF值,避免了伪造风险,但丢失了原始客户端信息。更平衡的方案是结合real_ip模块先修正$remote_addr,再用proxy_set_header X-Real-IP $remote_addr传递一个单一的、可信的IP给后端。
最后,调试IP传递问题时,建议在后端应用或Nginx日志中同时记录$remote_addr、$http_x_forwarded_for和$http_x_real_ip,对比分析每层的变化。可以使用curl -H "X-Forwarded-For: 8.8.8.8" http://yourdomain.com来模拟伪造头,观察Nginx如何处理。只有理解了数据流,才能设计出既满足业务又具备安全性的IP传递方案。
Nginxremote_addrproxy_add_x_forwarded_for修改时间:2026-09-17 14:31:12