ByteScope

502

HTTP 502 Bad Gateway 怎麼修

5xx 伺服器錯誤IETF 標準RFC 9110

應用程式前面那台代理替它回了話,因為應用程式自己的回應沒來、中途斷掉,或根本不是合法的 HTTP。

為什麼會看到 502,以及該怎麼處理

同樣三個數字,你站在請求的哪一邊,它就是不同的問題。直接讀你自己那一塊。

用 curl 重現 502

對著出錯的那個網址跑一次。它只印狀態碼、不印錯誤頁,所以你看到的是伺服器真正說了什麼,而不是瀏覽器畫出來的東西。

curl
curl -sS -o /dev/null -D - -w '\n%{http_code} in %{time_total}s\n' https://example.com/api/

`-sS` 會把進度列消掉但留下錯誤訊息,`-o /dev/null` 把那頁 HTML 錯誤頁丟掉,`-D -` 把回應標頭印到標準輸出,`-w` 則在傳輸結束後印出狀態碼和實際耗時。這份輸出裡有三樣東西決定你下一步看哪裡。狀態行確認它真的是 502,而不是應用程式自己回的 500。標頭那一段指名作者——`server: nginx` 而且沒有 `via`,那是你自己的代理;`server: cloudflare` 加上一個 `cf-ray`,那是 Cloudflare,而那個 `cf-ray` 的值就是他們客服會跟你要的編號。至於耗時,那是跟 504 分家的關鍵:被拒絕或壞掉的 upstream 在毫秒內就回,逾時的那種則是照著計時器回——nginx 預設的 `proxy_read_timeout` 是 60 秒,Cloudflare 則是撐到 125 秒才丟出自己的 524。要證明問題在代理而不在來源機,同一行加上 `--resolve example.com:443:203.0.113.10`,它會照樣送原本的主機名稱和 SNI,但直接連到你指定的來源位址。

502 實際上在講什麼

RFC 9110 §15.6.3 把 502 定義成:一台以閘道或代理身分運作的伺服器,在試著完成請求時,從它連進去的內部伺服器收到了不合法的回應。從這個寫法可以推出兩件事,而且兩件都會改變你該看哪裡。第一,寫出這個狀態碼的是中間那一層——nginx、HAProxy、Envoy、負載平衡器、Cloudflare——不是你的應用程式,而且你的應用程式很可能根本沒被執行到。第二,「不合法的回應」比「錯誤的回應」涵蓋得寬得多:TCP 連線被拒、回應標頭還沒收完連線就斷了、代理看不懂的狀態行、大到超過代理緩衝區的標頭,全都算;但應用程式成功回了自己的 500 不算,因為那是一個合法的回應,代理會原封不動轉出去。502 也不是逾時:RFC 9110 §15.6.5 把「沒有及時收到回應」留給了 504。這個區別附帶一支碼表——502 通常在毫秒內就回來,504 一定是在某個計時器剛好走完的那一刻回來。最後,502 不在 RFC 9110 §15.1 那份可依啟發式規則快取的清單裡,所以中間裝置不會自作主張把它存起來;而 nginx 預設會在 `error` 和 `timeout` 時換下一台 upstream,但除非你在 `proxy_next_upstream` 明寫 `non_idempotent`,否則它不會替非冪等的請求這麼做。

502 最常被搞混的幾個代碼

容易搞混的怎麼分
504兩者都是代理在報告 upstream 的狀況,而界線 RFC 9110 已經幫你畫好了:502 是不合法的回應,504 是沒有及時到的回應。讀碼表比讀字面有用。502 通常遠不到一秒就回來,因為 upstream 拒絕了連線或把它切了;504 會在一個整數上回來——nginx 預設的 `proxy_read_timeout` 是 60 秒、Cloudflare 是 125 秒——因為有人坐在那裡把計時器等完了。
500500 是應用程式承認自己失敗了,502 是代理在報告應用程式從來沒交出一個可用的答案。如果你框架的錯誤處理器有跑到,狀態碼就是 500,它的紀錄裡會留著那個例外,而代理會把那個回應原封不動轉出去。502 常常代表工作行程在任何處理器跑起來之前就死了,所以應用程式的紀錄是空的,唯一留下痕跡的只有代理的紀錄。
503503 說的是伺服器「暫時」無法處理這個請求,而且通常是刻意的——維護模式、健康檢查把後端拉出輪替、限流器擋下來。RFC 9110 允許 503 帶上 `Retry-After`,502 沒有這種慣例。所以 503 代表有東西決定要拒絕你,502 代表根本沒有東西答得出話。
521521 是 Cloudflare 自己的狀態碼,不是 IETF 的,而它存在的理由正是為了不讓這種情況被籠統地報成 502:它代表 Cloudflare 連到你來源機的連線被直接拒絕。看到 521 而不是 502,等於一步就把問題縮小到來源機的監聽或防火牆,而這就是 520 到 526 這一段當初被發明出來的原因。
nginxnginx 在把 502 寫給用戶端之前,會先把原因寫進自己的錯誤紀錄,而那一行比三位數字具體太多了。`connect() failed (111: Connection refused)`、`upstream prematurely closed connection`、`upstream sent too big header`、`no live upstreams` 是四個完全不同的問題,但送到瀏覽器上長得一模一樣。
原因片語
Bad Gateway
類別
5xx 伺服器錯誤
定義出處
RFC 9110
標準
IETF 標準

站上可以搭配的工具

這些全都在你的瀏覽器裡跑,不會上傳任何東西。

相關狀態碼

查 502 的時候,通常也會順手看一下這幾個。

常見問題

怎麼知道這個狀態碼是哪一台伺服器回的?

去讀回應標頭,不要讀那個網頁。curl -sS -o /dev/null -D - https://example.com/path 會只印標頭、不印內容,而 serverviacf-ray 三個加起來,就能指出是哪一層回答的。有 cf-ray 的值代表這個回應是 Cloudflare 處理的,那個值也是他們客服會跟你要的編號。想把邊緣節點整個排除掉,就加上 --resolve example.com:443:203.0.113.10 再打一次:它會照樣送出原本的主機名稱和 SNI,但直接連到你指定的來源位址——答案變了,就代表邊緣節點和來源機講的不是同一件事。

數字後面那句原因片語(像 Not Found)重要嗎?

不重要,而且絕對不要拿它來做判斷。RFC 9112 要求用戶端忽略原因片語,伺服器可以隨意改它,而 HTTP/2 和 HTTP/3 根本沒有這個欄位——所以在 HTTP/1.1 上是 404 Not Found,到了 HTTP/2 就只剩一個 404。這個網站會寫出登記在案的片語,是因為那是大家會拿去搜尋、也是會出現在 HTTP/1.1 紀錄裡的字串,不是因為有任何軟體依賴它。

這個代碼失敗的請求該不該重試?

5xx 和 429 可以重試,4xx 不要——下一次送過去也不會有任何不同。如果回應帶了 Retry-After 就照它做,RFC 9110 定義它就是為了這件事,值可以是秒數也可以是一個時間點。沒有的話,就用帶抖動的指數退避加上一個硬上限,而且只對冪等的方法這樣做:一個被重試的 POST 可能會刷兩次卡。對一台已經在失敗的伺服器丟出重試風暴,是短暫事故變成長時間事故最常見的那條路。

我可以讓自己的伺服器改回別的狀態碼嗎?

可以,唯一要守的規則是那個代碼必須是真的。所有會自動讀你回應的東西——搜尋引擎、監控、快取、用戶端的重試邏輯——都只看那個數字,從來不看那一頁寫了什麼。用 200 回一頁錯誤訊息,會把故障從你自己的警報裡藏起來;用 200 回一頁不存在的內容,會讓錯誤被當成內容收進索引;而對一個只是格式不對的請求回 500,會把值班的人送去堆疊裡錯的那一半。

502 還是卡住嗎?看完整的狀態碼對照表,或是回到上面,讀為你這一端寫的那一塊。