当Nginx作为反向代理部署在客户端与源站之间时,请求链路中会经过至少一次头重组。客户端发来的原始头不会原封不动地转发给上游,而由Nginx根据配置决定哪些字段保留、哪些字段补齐、哪些字段丢弃。对于后续的日志审计、应用安全策略和链路追踪来说,X-Forwarded-For、X-Real-IP、X-Forwarded-Proto、Proxy-Connection这些代理标准头承载了客户端真实信息。如果回源时没有透传,或者日志未记录,问题排查就会陷入盲区。本文重点说明在Nginx回源场景下,如何统一管理与记录这些Proxy-*标准头。

一、代理标准头的作用边界
反向代理中的标准头大致分为两类。一类是X-Forwarded-*系列,属于事实标准,由Squid等早期代理引入,后来被各层代理广泛支持。X-Forwarded-For记录客户端及每一跳代理的IP列表,X-Forwarded-Proto标识原始请求使用的是HTTP还是HTTPS,X-Forwarded-Host保存客户端请求中的Host信息。另一类是以Proxy-开头的RFC标准头,例如Proxy-Authenticate和Proxy-Authorization用于代理认证场景,Proxy-Connection则用于兼容HTTP/1.0时代的代理连接管理。
这些头并不是在所有场景下都需要透传。比如上游服务不需要代理认证时,Proxy-Authorization应当被剥离,避免安全信息泄露。日志层面则相反,即使应用不消费某个头,访问日志也可以把它记下来,供后续安全审计使用。因此配置时需要区分“透传到后端”和“记录到日志”两个动作。
在多层代理结构中,X-Forwarded-For的值是不断追加的,源站看到的可能是客户端IP, 代理1 IP, 代理2 IP这样的列表。如果不理解拼接规则,只取第一个或最后一个IP都可能出错。Nginx的$proxy_add_x_forwarded_for变量会自动在已有X-Forwarded-For值基础上追加当前客户端地址,这比直接使用$remote_addr覆盖更加合理。
二、回源时透传与覆盖Proxy-*头
Nginx默认的proxy_set_header指令只设置了Host和Connection两个头,并不会自动转发所有客户端原始头。要让X-Forwarded-For等字段出现在回源请求中,必须在location或server块中显式声明。下面是一段常见的回源透传配置:
location /api/ {
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_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $http_host;
proxy_set_header Proxy-Connection $http_proxy_connection;
proxy_pass http://backend_pool;
}
这里使用了Nginx内建变量$scheme来识别当前请求协议,用$http_host获取客户端请求中的Host头。对于Proxy-Connection,如果需要保留客户端原始值,可以使用$http_proxy_connection;但更常见的做法是不透传该头,因为它可能干扰HTTP/1.1的长连接管理。上游服务通常不依赖这个非标准字段。
需要注意的是,X-Forwarded-For使用$proxy_add_x_forwarded_for时,如果客户端已经携带了该头,Nginx会追加,不会覆盖。如果希望源站只信任最近一跳代理填充的值,可以在上游再解析列表,取倒数第二个IP作为客户端IP。另一种做法是结合real_ip模块在Nginx层提前还原真实地址,后续日志和$remote_addr都会是客户端IP,这部分在第四节展开。
三、在日志格式中记录这些标准头
默认的combined日志格式不会输出X-Forwarded-For、Proxy-Connection等头。要追踪回源链路中的完整信息,需要自定义log_format。Nginx访问日志可以使用$http_名称语法读取任意请求头的值,比如$http_x_forwarded_for对应X-Forwarded-For头,$http_proxy_connection对应Proxy-Connection头。
以下配置定义了一个名为proxy_debug的日志格式,专门用于排查回源问题:
log_format proxy_debug '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for" '
'"$http_x_forwarded_proto" "$http_x_forwarded_host" '
'"$http_proxy_connection" "$http_proxy_authorization"';
access_log /var/log/nginx/proxy_access.log proxy_debug;
将access_log指向独立文件可以避免与常规访问日志混淆。部分日志系统对引号敏感,可以在log_format中使用|或空格作为分隔符,但需要保证后续解析规则一致。如果记录了$http_proxy_authorization,要小心它可能包含敏感凭证,日志文件权限应收紧,或者对值做脱敏处理。
当Nginx前方还有CDN或负载均衡时,$remote_addr可能只是上一跳设备的地址,真正的客户端IP隐藏在X-Forwarded-For中。此时日志里同时输出$remote_addr和$http_x_forwarded_for就很有必要,便于对比分析。
四、real_ip模块修正日志中的客户端地址
如果希望日志中的$remote_addr直接显示真实客户端IP,而不是代理节点IP,需要启用ngx_http_realip_module模块。该模块会解析指定的代理头,并修改$remote_addr的值。配置如下:
set_real_ip_from 10.0.0.0/8; set_real_ip_from 172.16.0.0/12; set_real_ip_from 192.168.0.0/16; real_ip_header X-Forwarded-For; real_ip_recursive on;
以上配置表示只信任来自内网的三类地址,从X-Forwarded-For头中从右向左递归查找第一个不属于可信代理的IP作为客户端地址。real_ip_recursive on在多级代理场景尤其重要,它会让Nginx跳过已知代理IP,找到真正的外网客户端地址。
启用real_ip后,$remote_addr会变为真实客户端IP,X-Forwarded-For通常保持不变。如果此时仍然用$proxy_add_x_forwarded_for回源,上游看到的X-Forwarded-For会再多一个IP,这是正常且合理的,因为上游也需要知道经过了Nginx这一跳。日志中则直接输出$remote_addr即可获得客户端地址,无需再从X-Forwarded-For中手工截取。
不过real_ip改变了$remote_addr后,原先基于IP的访问控制规则也会受影响。例如使用allow、deny指令时要确认生效对象是否符合预期。另外如果客户端伪造了X-Forwarded-For,而set_real_ip_from没有限定信任范围,真实IP可能被覆盖,因此上面的内网网段限制不能省略。
Nginx日志回源Proxy-*标准头反向代理修改时间:2026-09-27 18:48:05