在CDN、负载均衡或反向代理后的源站服务器上,Nginx接收到的TCP连接往往来自代理节点,而不是最终用户。此时如果直接使用Nginx内置变量$scheme记录访问日志,得到的是回源连接本身的协议,例如边缘节点使用HTTP回源时,所有用户请求都会显示为http,HTTPS请求的统计就此丢失。要还原真实情况,需要关注X-Forwarded-Proto请求头。该头由前端代理写入,用于告知后端原始客户端使用的协议。但如何安全、准确地将它记录到Nginx日志中,并避免被伪造干扰,是许多源站配置中容易忽视的环节。

一、X-Forwarded-Proto在回源链路中的传递机制
当用户通过HTTPS访问CDN节点时,CDN作为反向代理终结TLS,解密请求后在回源阶段重新发起连接。如果回源仍然使用HTTPS,Nginx的$scheme自然为https;但出于成本或内网信任考虑,大量CDN和负载均衡默认使用HTTP回源,源站看到的便是http。因此仅凭连接协议无法还原用户行为。X-Forwarded-Proto正是为了解决这一信息丢失而设计,它通常以X-Forwarded-Proto: https的形式附加在回源请求中。
需要明确的是,X-Forwarded-Proto并不是标准HTTP头部,而是事实上的行业约定,常见于NGINX、HAProxy、各类CDN以及云负载均衡。只要代理层正确配置,例如Nginx反代中设置proxy_set_header X-Forwarded-Proto $scheme;,源站便能从$http_x_forwarded_proto中读取原始协议。然而这个头可以被任何客户端直接发送,因此它在日志中的应用必须经过校验,否则任何人都可以通过手动设置头为https来污染日志统计,甚至绕过某些基于协议判断的安全策略。
在多层回源链路中,每一层都可能追加或覆盖该头。通常约定由最靠近用户的边缘节点设置或覆盖,以真实描述用户到边缘的协议;后续代理层不应再修改。若后端代理错误地将其改写为回源连接协议,就会再次产生假数据。这也是很多日志协议不准确问题的根源。
二、配置Nginx日志记录真实回源协议
第一步是定义新的日志格式。可以在http块中使用log_format指令,将$http_x_forwarded_proto作为独立字段输出。以下配置在标准组合日志基础上增加了回源协议列:
log_format main_with_proto '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_proto"';
然后在需要记录该格式的server或location块中通过access_log指定:
server {
listen 80;
server_name origin.ipipp.com;
access_log /var/log/nginx/origin.access.log main_with_proto;
...
}
这种直接记录方式虽然简单,但无法防止伪造头,因为$http_x_forwarded_proto会原样返回客户端提交的内容。如果攻击者发送X-Forwarded-Proto: https,日志就显示https。对于安全审计来说,这样的字段只能作为参考,不能作为信任来源。更稳妥的做法是引入map指令,对头值做白名单校验,仅在值严格等于http或https时才采信,否则回退到$scheme。
map $http_x_forwarded_proto $log_xfp {
default $scheme;
http http;
https https;
}
上述映射会把所有非http、https的输入,包括空值、多值或者恶意构造的字符串,统一回退为当前连接协议。日志格式中就可以使用更安全的$log_xfp变量:
log_format safe_main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "proto=$log_xfp"';
如果前端代理可能返回带空格的多值头,map默认分支同样会回退到连接协议,避免日志字段出现不易解析的空白。对于只需要知道到底是不是https的二值场景,也可以只保留https匹配,其余全部视作http。
三、防止伪造与多层代理加固
日志一旦被外部可控输入污染,不仅破坏统计,还可能误导安全设备。比如某些Web应用防火墙或自定义脚本会根据该头判断请求是否为加密连接,如果源站无条件信任,攻击者就能通过伪造https头绕开强制跳转逻辑。因此在日志记录和业务判断中都应坚持白名单原则,不接受任何超出http和https的值。这里使用map的做法同样可以应用到后续的反向代理转发中。
多层代理下还需要固定覆盖策略。一般只允许最外层边缘节点写入或覆盖X-Forwarded-Proto,内部代理不应再次修改。以Nginx作为中间层为例,若它自身收到的$http_x_forwarded_proto已经存在且可信,就不要用$scheme覆盖;如果确实要追加,应保留原始链路信息。常用策略是在边缘Nginx上配置:
server {
listen 443 ssl;
server_name edge.ipipp.com;
location / {
proxy_pass http://origin_upstream;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
但源站Nginx如果作为二次代理继续转发给应用服务器,就不应再次无条件写X-Forwarded-Proto $scheme。此时可以先经过map校验得到可信值,再决定是否传递:
map $http_x_forwarded_proto $forwarded_proto {
default $scheme;
http http;
https https;
}
server {
listen 80;
server_name origin.ipipp.com;
location / {
proxy_pass http://backend;
proxy_set_header X-Forwarded-Proto $forwarded_proto;
}
}
这样既防止客户端伪造,又避免每一层都把回源协议当成用户协议重新覆盖。该映射逻辑与日志记录共用同一个可信变量,减少配置分散带来的不一致。
四、验证日志与常见问题排查
完成配置后应先通过nginx -t检查语法,再执行nginx -s reload平滑加载。测试时可以分别模拟正常回源和伪造头场景。正常请求由前端代理转发并带X-Forwarded-Proto: https,查看日志最后字段应显示proto=https。直接向源站发送一个伪造头,例如:
curl -H "X-Forwarded-Proto: https" http://origin.ipipp.com/test
如果map白名单配置正确,该请求因为并非来自真实前端,头值仍然会被记录为https,因为该值本身合法。要验证非法输入的回退效果,可以发送X-Forwarded-Proto: foo,日志应回退为$scheme,即回源连接的http。这一点可能让部分运维困惑:白名单能防止非http、https字符串,却不能区分头是否来自真实代理。要解决后者,需要结合real_ip模块只信任已知代理IP,在此基础上接收X-Forwarded-For和X-Forwarded-Proto等头。
常见问题包括日志中协议列为空、显示http但实际用户是https、或者多出不可见字符。空值通常因为前端代理未添加该头;显示http往往是回源协议本身是http且前端未覆盖;多出字符则可能来自中间层重复追加。排查时可以在location中用return加add_header输出变量实际值,快速确认Nginx收到的内容。日志格式中变量顺序应保持稳定,避免后续使用awk或ELK解析时错位。
Nginx日志X-Forwarded-Proto回源协议修改时间:2026-08-22 04:45:40