ByteScope

504

HTTP 504 Gateway Timeout 怎麼修

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

代理等某台 upstream 等到沒耐心了,於是替它回話——所以真正指認兇手的不是狀態碼,而是耗掉的時間,那永遠是某個人設定的逾時。

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

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

用 curl 重現 504

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

curl
curl -sS -o /dev/null -D - --max-time 180 -w '\n%{http_code} connect=%{time_connect}s ttfb=%{time_starttransfer}s total=%{time_total}s\n' https://example.com/slow

`--max-time 180` 設定的是 curl 對整個操作的上限,而且它必須明顯比伺服器的計時器長,否則會是 curl 先中斷,那你除了「你比較沒耐心」之外什麼都學不到。三個時間就是診斷。`%{time_connect}` 是 TCP 連線完成的時刻,`%{time_starttransfer}` 是第一個位元組抵達的時刻,`%{time_total}` 是結束;一個 504 會有很快的 connect,而 total 會落在一個整數上。把那個數字對到某個有文件的預設值,你就知道是哪一層了:大約 60 秒是 nginx 的 `proxy_read_timeout` 或 Application Load Balancer 的閒置逾時,兩者預設都是 60;大約 125 秒是 Cloudflare 的讀取逾時——而那一個回報的是 524、不是 504,所以一個落在 125 秒的 504 不是 Cloudflare 的。total 明顯比任何預設值短,代表有人刻意設過那個計時器,於是範圍就縮到某個設定檔、而不是某個產品。接著把層次拆開:`--resolve example.com:443:203.0.113.10` 會直接連到來源機的位址,同時照樣送出原本的主機名稱和 SNI,所以如果來源機 90 秒回得出來、邊緣節點卻在 60 秒就放棄,那就是這件事真的慢,計時器只是報信的。跑這行指令的同時盯著來源機自己的存取紀錄——一個出現在那裡、而且耗時比逾時還長的請求,就是「代理不再等之後那件事還是做完了」的證據,而這正是在這裡重試寫入很危險的原因。

504 實際上在講什麼

RFC 9110 §15.6.5 把 504 定義成:一台以閘道或代理身分運作的伺服器,沒有「及時」從它為了完成請求而必須存取的那台上游伺服器收到回應時,回出來的狀態。跟 §15.6.3(把 502 留給不合法的回應)擺在一起讀,「及時」這一個詞就是全部的分野,而且它附帶一支碼表:502 在毫秒內回來,因為有東西拒絕或斷掉了;504 則在一個整數上回來,因為有個計時器走完了。那些整數是有文件的,而且能指認出是哪一層。nginx 的 `proxy_read_timeout`、`proxy_connect_timeout` 和 `proxy_send_timeout` 預設都是 60 秒,而且它註明連線逾時通常無法超過 75 秒,因為作業系統自己的連線嘗試就在那裡結束。AWS 的文件寫著 Application Load Balancer 的連線閒置逾時預設是 60 秒、可設定範圍 1 到 4000,並建議在每個閒置週期走完之前至少送一個位元組,免得長時間的上傳被切斷。Cloudflare 自己的讀取逾時是 125 秒,而且它產生的是 524、不是 504。所以一個大約落在一分鐘的 504,是 nginx 或 ALB 的預設計時器;一個落在其他整數上的 504,是某個人刻意設的計時器。有兩個性質會改變你的下一步。504 不是 RFC 9110 §15.1 定義為可依啟發式規則快取的狀態碼之一,所以它是每個請求重新產生的,不會被一個沒被交代過的中介裝置存起來。另外,跟 502 不一樣,504 完全沒有攜帶「那件事有沒有做完」的證據:代理放棄的時候,upstream 手上還握著那條連線,所以它很可能一秒之後就完成了那個請求、把被要求的東西全部寫進去了。這讓「盲目重試一個非冪等的請求」成為這裡代價最高的錯誤。nginx 把同樣的謹慎寫進了設定——`proxy_next_upstream` 預設是 `error timeout`,所以逾時的確會換下一台伺服器;但非冪等方法的請求一旦送出去就不會被轉給下一台,除非你明寫 `non_idempotent`,那是 nginx 1.9.13 開始才有的參數。

504 最常被搞混的幾個代碼

容易搞混的怎麼分
502兩者都是中介裝置在報告 upstream 的狀況,而 RFC 9110 把界線畫得很精準:§15.6.3 是不合法的回應,§15.6.5 是沒有及時到的回應。碼表比字面更快給你答案。502 在毫秒內回來,因為連線被拒或被切斷;504 則是照計時器回。後果比標籤更重要:502 之後,那個請求幾乎可以確定什麼都沒做;504 之後,upstream 有可能已經把它做完了。
524524 是 Cloudflare 自己的代碼、不是 IETF 的,而且它只用在一個地方的這一種情況:Cloudflare 連到來源機了,但在它 125 秒的讀取逾時內沒有等到任何 HTTP 回應。所以一個穿過 Cloudflare 抵達的 504,是別的東西產生的——你自己的代理,或某台以閘道身分運作的來源機——只是被轉過來。如果你想調大逾時,要知道只有 Enterprise 方案的區域才能改 Cloudflare 的,而且最多到 6,000 秒。
503503 是事先做出的拒絕,504 是等到沒得等了。RFC 9110 §15.6.4 允許 503 帶 `Retry-After`,因為伺服器大概知道自己什麼時候會回來;504 沒有任何對應的東西,因為代理根本不知道 upstream 在幹嘛。如果你的降載機制產生的是 504,那它就不是在降載——它是把請求排隊排到被計時器殺掉,而 upstream 那份工還是照做了。
nginx錯誤紀錄會指名計時器,而那三則訊息值得分清楚。`while connecting to upstream` 是 `proxy_connect_timeout`,指向網路或沒有東西在監聽;`while reading response header from upstream` 是 `proxy_read_timeout`,指向應用程式慢;`while sending request to upstream` 是 `proxy_send_timeout`,通常代表一個大的請求 body 對上一個卡住的讀取端。三個預設都是 60 秒,而連線逾時通常無法超過 75 秒。
ELB 504AWS 的文件寫著 Application Load Balancer 的連線閒置逾時預設 60 秒、可調整範圍 1 到 4000,而目標在那之內沒有回應就回 504。他們自己對長時間操作的建議,是在每個閒置週期走完之前至少送一個位元組——這就是為什麼串流的回應活得下來、安靜的回應會死。目標端的 keep-alive 也要看:AWS 註明它不應該比負載平衡器的閒置逾時還短。
原因片語
Gateway Timeout
類別
5xx 伺服器錯誤
定義出處
RFC 9110
標準
IETF 標準

站上可以搭配的工具

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

相關狀態碼

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

常見問題

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

去讀回應標頭,不要讀那個網頁。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,會把值班的人送去堆疊裡錯的那一半。

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