为了账号安全,请及时绑定邮箱和手机立即绑定

用Nginx代理总404?换个网关就好?罪魁祸首竟是这个Host头!

标签:
Nginx

前言:谁没被Nginx代理的404坑过?

前几天帮业务排查问题,差点没被一个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

诡异现象背后:Tomcat和Gateway,对Host的态度差太多

为啥同样的Nginx配置,代理Tomcat就404,走Gateway就正常?

关键差别,就在于后端服务怎么处理「Host请求头」

1. Spring Cloud Gateway:我认路径,不认Host

Gateway的脾气很随和:它路由请求时,主要看「路径前缀」(比如/user-service/),顶多再看看请求头里的特殊标记(比如X-Request-Id)。哪怕你给它的Host头乱填,只要路径对了,它就能精准找到后端服务。

大白话就是:Gateway找服务,靠的是「路标」(路径),不是「门牌号」(Host)

2. Tomcat:Host不对?门都没有!

Tomcat就死板多了:它天生认「Host头」当门牌号。收到请求时,会先扒拉Host头,根据这个值判断该把请求分给哪个虚拟主机。如果Host头和它预期的不一样,哪怕路径再对,它也会摆摆手说“没这个地址”(404)。

大白话就是:Tomcat找服务,必须先对「门牌号」(Host),不对就拒客

回到开头的问题:

  • 当Nginx代理Tomcat时,配置里用了proxy_set_header Host $http_host(也就是把客户端的Host头原封不动传给Tomcat)。这时候Tomcat拿到的Host是nginx_url,但它自己预期的Host可能是tomcat_url,门牌号对不上,自然404。

  • 换成Gateway后,Gateway不管Host是啥,只要路径里有/user,就知道该转发给user-service,所以能正常返回。

搞懂这3个变量,Host配置再也不踩坑

既然问题出在proxy_set_header Host,那到底该填啥?Nginx里有3个容易混淆的变量:$http_host$host$proxy_host,分清它们,90%的代理问题都能解决。

1. $http_host:客户端发啥,它就传啥(带端口)

这个变量就是个“传声筒”,客户端请求头里的Host是啥,它就原样转发,包括端口号。比如客户端发example.com:8080,它就传example.com:8080

但有个坑:如果客户端没发Host头,它就空了。

适用场景:必须严格保留客户端原始Host信息时(比如需要根据客户端Host做特殊逻辑)。

2. $host:自动“修门牌号”(去端口)

这个变量更智能:

  • 优先用客户端发的Host头,但会自动去掉端口(比如example.com:8080变成example.com);
  • 如果客户端没发Host头,就用Nginx自己监听的服务器名(比如Nginx配置的server_name)。

适用场景:大部分常规Web应用,需要一个干净的主机名时。

3. $proxy_host:后端服务器的“真实地址”(带端口)

这个变量只在反向代理时生效,值就是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”了。

最后叮嘱:这3个原则记牢,少走90%的弯路

  1. 代理到Tomcat、Nginx等“认Host”的服务时,优先用$proxy_host(告诉它真实地址);
  2. 代理到Gateway、API网关等“认路径”的服务时,用$host更稳妥(简化主机名);
  3. 除非明确需要保留客户端原始Host(比如多租户按Host区分),否则别轻易用$http_host(容易带端口坑)。

如果你也被Nginx代理的404折磨过,或者团队里有人总踩这类坑,欢迎转发给他们——有时候解决问题的关键,就藏在一个被忽略的配置里。

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
微信客服

购课补贴
联系客服咨询优惠详情

帮助反馈 APP下载

慕课网APP
您的移动学习伙伴

公众号

扫描二维码
关注慕课网微信公众号

举报

0/150
提交
取消