博客 / 詳情

返回

用Nginx代理總404?換個網關就好?罪魁禍首竟是這個Host頭!

前言:誰沒被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折磨過,或者團隊裏有人總踩這類坑,歡迎轉發給他們——有時候解決問題的關鍵,就藏在一個被忽略的配置裏。

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.