在Nginx反向代理的实际使用中,Host头的处理是最容易被忽视又最容易出问题的环节。后端服务通常依赖请求头中的Host来判断访问的是哪个虚拟主机、哪个站点,一旦Nginx转发时携带的Host与后端预期不一致,就会出现页面404、跳转到默认站点、日志记录混乱等一系列奇怪现象。本文结合配置示例,详细讲解回源Host头的设置方法、原理以及排查思路。

一、默认情况下Nginx回源时发送的Host是什么
很多使用者以为Nginx会把客户端原始请求的Host原样传给后端,其实并非如此。Nginx作为反向代理转发请求时,默认的Host值取的是proxy_pass指令中配置的上游地址。举例来说,如果配置写的是proxy_pass http://192.168.1.10:8080;,那么发给后端的请求头中Host就是192.168.1.10:8080,而不是用户浏览器里输入的域名。
这种默认行为在简单场景下没问题,但后端如果是Apache、Tomcat或者基于域名区分站点的Web服务,就会因为Host对不上而匹配到错误的虚拟主机。比如后端Tomcat上部署了多个应用,靠Host来区分,收到的却是IP形式的Host,自然无法路由到正确的应用,最终返回404或者默认页面。
要改变这个行为,需要显式使用proxy_set_header指令来覆盖。下面是常见的写法:
server {
listen 80;
server_name www.ipipp.com;
location / {
proxy_pass http://192.168.1.10:8080;
# 将回源请求的Host设置为客户端请求的原始Host
proxy_set_header Host $host;
# 不带端口的形式,避免后端匹配失败
# proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}这里有两个变量值得区分:$host和$http_host。前者是Nginx解析后得到的规范化的主机名,不带端口,且当请求行中携带了绝对URI时以URI中的主机名为准;后者则是直接取客户端请求头中Host的原始值,通常带端口。绝大多数场景用$host更稳妥,因为它不带端口,后端做域名匹配时不容易出错。
二、Host头与X-Forwarded-Host的区别及传递链路
在多层代理架构中,仅仅设置Host还不够。Host头是发给后端的实际请求头,会被每一层代理覆盖;而X-Forwarded-Host的作用是记录客户端最初请求的主机名,供后端还原真实访问域名使用。两者职责不同,不能互相替代。
假设架构是客户端到CDN,再到Nginx,最后到源站。如果每一层都把Host改写成自己的上游地址,源站就拿不到任何域名信息了。正确的做法是:第一层代理把客户端Host写入X-Forwarded-Host并透传,后续层继续追加或透传,同时在最后一层根据业务需要决定Host最终写什么值。典型配置如下:
location / {
proxy_pass http://backend;
# 回源Host设置为业务域名
proxy_set_header Host www.ipipp.com;
# 传递客户端原始Host,供后端还原
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
}后端应用如果需要生成带正确域名的链接(比如重定向、静态资源地址),可以优先读取X-Forwarded-Host,取不到时再退回Host。很多框架例如Spring Boot、Django都有对应的forwarded头处理配置,记得在Nginx侧把这些头传全,否则应用层再怎么配也没数据可用。
需要注意一点:在CDN回源场景中,回源Host(即CDN发给源站的Host)必须与源站服务器上配置的站点域名一致。如果Nginx作为源站,要确保server_name能匹配到CDN回源时携带的Host,否则请求会落到默认server块,出现内容错乱。
三、在日志中记录回源Host便于排查问题
排查回源问题时,光看现象不够,最直接的办法是把相关的Host变量写进访问日志。Nginx的日志格式支持自定义变量,可以同时记录客户端请求的Host和实际转发使用的Host,对比两者就能快速定位配置问题。
log_format proxy_log '$remote_addr - $upstream_addr [$time_local] '
'"$request" $status $body_bytes_sent '
'req_host=$host fwd_host=$http_x_forwarded_host '
'upstream_host=$proxy_host rt=$request_time';
server {
listen 80;
server_name www.ipipp.com;
access_log /var/log/nginx/proxy_access.log proxy_log;
location / {
proxy_pass http://192.168.1.10:8080;
proxy_set_header Host $host;
}
}其中$proxy_host这个变量比较特殊,它表示proxy_pass中定义的上游主机和端口,也就是默认情况下会作为Host发送出去的值。把这个变量打进日志,就能清楚看到在没有proxy_set_header Host时,后端实际收到的是什么。如果日志里req_host是域名而upstream_host是IP加端口,基本可以断定回源Host没有正确设置。
另外,如果想在后端侧验证收到的请求头,可以在源站临时加一条规则,把请求头全部记录下来,或者用tcpdump抓包确认:
tcpdump -i eth0 -A -s 0 'tcp port 8080 and host 192.168.1.5' | grep -i 'host:'
抓包是最可靠的验证手段,能排除中间所有环节的干扰,直接看到线上真实流转的请求头内容。
四、配置生效优先级与常见踩坑点
proxy_set_header的继承规则是一个经典坑。它属于数组类指令,只有在当前层级没有任何proxy_set_header的情况下才会继承上层配置;只要当前块里写了一条,上层的全部设置都不会被继承。也就是说,location里写了一个proxy_set_header X-Real-IP,而Host的设置写在server块里,那么这个location转发时Host就不会按server块的配置来,而是回到默认值。解决办法是要么把所有头都写在同一层级,要么使用include引入公共的头配置片段,保证一致性。
# /etc/nginx/proxy_params_common.conf
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;
# 在server或location中统一引入
location / {
proxy_pass http://backend;
include /etc/nginx/proxy_params_common.conf;
}其次是大小写与合法性的问题。HTTP/1.1规范要求Host必须携带,Nginx遇到非法Host头时会直接返回400。如果上游是通过upstream块定义的,默认Host会是upstream的名字而非具体地址,这一点在排查时容易被忽略。此外,配置修改后务必执行nginx -t检查语法再nginx -s reload,否则语法错误可能导致服务无法正常重载。
最后总结一下排查思路:先确认后端期望收到的Host值,再检查Nginx配置中的proxy_set_header Host是否生效(注意继承规则),然后通过日志和抓包验证实际发送的值,三层确认下来,绝大多数回源Host相关的问题都能定位并解决。合理设置回源Host不仅影响功能正确性,也让访问日志、统计报表中的域名信息更加准确,是反向代理配置中不可省略的一环。
Nginx日志proxy_set_header回源Host头修改时间:2026-09-13 00:44:41