前几天帮业务排查问题,差点没被一个Nginx代理的404搞破防。
场景很简单:前端用Nginx代理到Tomcat,访问nginx_url/web/user/hi直接404;但跳过Nginx,直接访问tomcat_url/user/hi,接口稳稳返回"hello hi"。更诡异的是,把代理地址换成Spring Cloud Gateway,同样的路径nginx_url/web/user/hi居然秒通了!
同样的Nginx配置,换个后端就好使?这到底是Nginx抽风了,还是Tomcat和Gateway有仇?
如果你也遇到过「直连后端正常,代理就报错」「换个中间件突然好了」的情况,那这篇文章一定要看完——其实问题的根儿,就藏在一个容易被忽略的配置里:proxy_set_header Host。
为啥同样的Nginx配置,代理Tomcat就404,走Gateway就正常?
关键差别,就在于后端服务怎么处理「Host请求头」。
Gateway的脾气很随和:它路由请求时,主要看「路径前缀」(比如/user-service/),顶多再看看请求头里的特殊标记(比如X-Request-Id)。哪怕你给它的Host头乱填,只要路径对了,它就能精准找到后端服务。
大白话就是:Gateway找服务,靠的是「路标」(路径),不是「门牌号」(Host)。
Tomcat就死板多了:它天生认「Host头」当门牌号。收到请求时,会先扒拉Host头,根据这个值判断该把请求分给哪个虚拟主机。如果Host头和它预期的不一样,哪怕路径再对,它也会摆摆手说“没这个地址”(404)。
大白话就是:Tomcat找服务,必须先对「门牌号」(Host),不对就拒客。
回到开头的问题:
proxy_set_header Host $http_host(也就是把客户端的Host头原封不动传给Tomcat)。这时候Tomcat拿到的Host是nginx_url,但它自己预期的Host可能是tomcat_url,门牌号对不上,自然404。/user,就知道该转发给user-service,所以能正常返回。既然问题出在proxy_set_header Host,那到底该填啥?Nginx里有3个容易混淆的变量:$http_host、$host、$proxy_host,分清它们,90%的代理问题都能解决。
这个变量就是个“传声筒”,客户端请求头里的Host是啥,它就原样转发,包括端口号。比如客户端发example.com:8080,它就传example.com:8080。
但有个坑:如果客户端没发Host头,它就空了。
适用场景:必须严格保留客户端原始Host信息时(比如需要根据客户端Host做特殊逻辑)。
这个变量更智能:
example.com:8080变成example.com);server_name)。适用场景:大部分常规Web应用,需要一个干净的主机名时。
这个变量只在反向代理时生效,值就是proxy_pass里填的后端地址(带端口)。比如proxy_pass http://tomcat:8080,那$proxy_host就是tomcat:8080。
它的核心作用:告诉后端“我真实的地址是啥”。
适用场景:后端服务(比如Tomcat)需要知道自己的真实地址时。
变量 | 本质 | 带不带端口 | 给后端的信息 |
|---|---|---|---|
$http_host | 客户端原始Host | 可能带 | “客户端认为你是这个地址” |
$host | 简化版主机名 | 不带 | “你的公开门牌号是这个” |
$proxy_host | 后端真实地址 | 带 | “你的真实地址是这个” |
知道了区别,再看开头的Nginx配置:
原来的配置用了proxy_set_header Host $http_host,相当于告诉Tomcat“客户端认为你是nginx_url”,但Tomcat只认自己的真实地址(比如tomcat:8080),所以404。
解决办法很简单:把Host改成$proxy_host,告诉Tomcat“你的真实地址在这”:
location /web/ {
proxy_pass $WEB_TOMCAT/user/;
proxy_set_header X-Real-IP $remote_addr;
# 关键修改:用$proxy_host传递后端真实地址
proxy_set_header Host $proxy_host;
proxy_set_header X-Forward-For $http_x_forwarded_for;
# 其他配置...
}改完之后,Tomcat收到的Host是$proxy_host(也就是$WEB_TOMCAT对应的真实地址),门牌号对上了,自然就返回“hello hi”了。
$proxy_host(告诉它真实地址);$host更稳妥(简化主机名);$http_host(容易带端口坑)。如果你也被Nginx代理的404折磨过,或者团队里有人总踩这类坑,欢迎转发给他们——有时候解决问题的关键,就藏在一个被忽略的配置里。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。