AWS帳號充值代辦 AWS CloudFront 報 504 Gateway Timeout:源站 Alpine/Nginx 保持連線設定排查
問題現象:CloudFront 回 504,不代表源站一定宕機
很多人第一次看到 CloudFront 回 504 Gateway Timeout,直覺就是源站掛了,或是程式太慢。其實這個判斷只對了一半。CloudFront 本身是邊緣代理,它在轉發請求給源站時,如果等不到連線建立、首字節回應,或等到一半連線被源站切掉,就可能直接吐出 504。表面上看起來是 AWS 出錯,真正的問題常常藏在源站入口的 Nginx、後端應用,甚至是容器環境的連線節奏。
如果你的源站是 Alpine 上的 Nginx,這類問題更容易被放大。不是因為 Alpine 天生有問題,而是它偏精簡,預設值少、工具少、可見度低,很多原本就不穩的設定,在這種環境裡更容易露出破綻。只要 CloudFront、Nginx、上游應用三者的 timeout 與 keepalive 沒對齊,就很容易在高峰期、部署期或偶發抖動時爆出 504。
先分清楚,是「慢」還是「斷」
排查前先把現象拆開。若請求只是變慢,但最終仍能完成,重點多半在資料庫、應用邏輯或快取命中率;若請求經常在固定秒數後失敗,則比較像 timeout;若日誌裡看到連線被提前關閉、upstream 中斷、RST、EOF 之類訊息,那通常就是連線管理出了問題。CloudFront 的 504 不一定意味著整個系統慢,更常見的是某一層在不該斷的時候斷了,或在該等的時候沒等夠。
這類 504 最常見的三個根因
一、源站回應超過 CloudFront 的等待時間
最直接的情況,就是源站真的慢。可能是應用執行時間過長、資料庫查詢卡住、外部 API 超時,或是 Nginx 反向代理到上游時等不到回應。CloudFront 並不知道你的應用是不是正在忙,它只知道自己在設定時間內沒有拿到結果,就會把請求視為失敗。這種狀況下,調高 CloudFront timeout 只能延後爆炸,不能解決問題本身。
二、源站或 Nginx 提前關閉 keepalive 連線
CloudFront 會盡量重用到源站的連線,這樣可以減少握手成本,也能提升吞吐。但如果 Nginx 的 keepalive_timeout 太短,或上游應用根本不接受長連線,CloudFront 下一次複用時拿到的可能已經是一條半開連線。這時候請求不一定馬上報 502,很多時候就是繼續等,最後變成 504。從外部看像是超時,實際上是連線生命週期沒有對齊。
三、協議模式不一致
CloudFront 到源站通常使用 HTTP/1.1 長連線;如果你的 Nginx 還在用 HTTP/1.0,或在反向代理時把 Connection 明確設成 close,keepalive 幾乎就被你自己關掉了。這種設定在低流量時不一定出事,一旦流量變大、連線變多、部署頻繁,問題就會變得很明顯。很多 504 不是系統真的撐不住,而是協議與超時的配合方式太粗糙。
AWS帳號充值代辦 為什麼 Alpine / Nginx 組合特別容易踩坑
AWS帳號充值代辦 Alpine 的特色是輕、快、乾淨,但也因為太乾淨,很多隱藏問題不會被包裝起來。當你把 Nginx 放進 Alpine 容器當源站入口,外面再接 CloudFront,平常小流量可能一切正常,一到高峰時就開始出現延遲、連線重建、偶發超時。這不是 Alpine 造成的故障,而是精簡環境把資源上限、連線上限、日誌線索都縮得很明顯。
另外一個常見背景是,Nginx 只是入口,後面還有 PHP-FPM、Node.js、Go、Java 或其他應用。Nginx 自己處理靜態檔案很快,但一旦請求要轉給上游,上游慢一點、卡一點,CloudFront 看到的就是整條鏈路慢。尤其在容器裡,CPU 配額、記憶體限制、檔案描述符上限、容器重啟與健康檢查,都可能讓連線保持時間變得不穩。這些細節不一定會讓你立刻報錯,但很容易把 CloudFront 推到 504 的邊緣。
排查不要急著改大 timeout,先把鏈路拆開
第一步:繞過 CloudFront 直接打源站
最基本的做法,是直接對源站發請求,看看同一條 URL 在沒有 CloudFront 的情況下是否穩定。若直連穩定,問題更可能出在 CloudFront 到源站之間;若直連也慢,那就不要再盯著 CloudFront,先查 Nginx 與上游應用。這一步的目的,是把責任邊界切清楚,而不是憑感覺猜。
直連時要看三件事:DNS 是否解析正確、TLS 握手是否正常、首字節時間是否異常。很多人只看總耗時,卻忽略真正卡住的是握手、排隊,還是應用本身的執行時間。只要這三段中的任一段變慢,CloudFront 都可能在前面先放棄。
第二步:看 Nginx error log 與 access log
Nginx 的錯誤日誌往往比 CloudFront 更接近真相。如果看到 upstream timed out、upstream prematurely closed connection、connect() failed、broken pipe 之類訊息,方向就很明確了。這些關鍵字分別對應上游超時、上游提前斷線、連線建立失敗與寫入失敗,都是 504 的常見前兆。
access log 則適合看整體耗時。若大部分請求都很快,只有少數請求突然拖很久,通常是上游偶發抖動、資料庫慢查詢、資源競爭或容器被搶占。若幾乎每一個請求都接近固定上限才失敗,則多半是 timeout 設得太死,或某一層固定在等不到的點上。
第三步:確認是否真的在使用長連線
很多人以為自己開了 keepalive,實際上只是把 Nginx 的空閒連線時間調大,但 proxy 到上游時仍然在用舊式設定。若 proxy_http_version 沒改成 1.1,或者 Connection header 仍然被設定為 close,Nginx 與上游之間就不會正常複用連線。CloudFront 看到的是連線一再建立與關閉,這種情況在低負載下不一定報錯,但在高併發下非常容易抖動。
AWS帳號充值代辦 Nginx 源站入口,建議先檢查這幾個設定
如果你的源站就是 Nginx,先不要急著去調 CloudFront。先確認 Nginx 的代理方式是否合理,特別是協議版本、Connection header、上游 keepalive 與各種 timeout 是否一致。下面是一個相對穩妥的起點,重點在於讓連線可以複用,而不是每次請求都重新握手。
http {
upstream app_backend {
server 127.0.0.1:8080;
keepalive 32;
}
server {
listen 80;
server_name example.com;
location / {
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_connect_timeout 5s;
proxy_send_timeout 30s;
proxy_read_timeout 30s;
proxy_pass http://app_backend;
}
keepalive_timeout 65s;
keepalive_requests 1000;
}
}
這段配置的核心不是把數字調得很大,而是把行為調一致。proxy_http_version 1.1 讓上游可以長連線,proxy_set_header Connection "" 避免把 close 傳下去,upstream keepalive 則讓 Nginx 到應用之間的連線可以反覆利用。若後端是 PHP-FPM、Gunicorn、uWSGI 或 Node.js,也要對照它們自己的空閒超時與最大連線數,不要只修 Nginx,卻讓上游在另一端偷偷斷線。
另外,keepalive_timeout 與 proxy_read_timeout 不是同一件事。前者是空閒連線可保留多久,後者是 Nginx 等待上游回應多久。很多人把兩者混在一起,結果以為自己已經把連線撐長了,實際上只是讓空閒連線活得久一點,真正的請求等待時間還是太短。
CloudFront 端也要一起對齊
CloudFront 並不是完全被動的轉發器,它對源站連線也有自己的等待模型。像 origin response timeout、origin keep-alive timeout 這些參數,都會影響邊緣節點願意等你多久、願意重用連線多久。如果源站本來就需要較長的處理時間,而 CloudFront 的等待時間太短,就算 Nginx 配得再漂亮,前端仍然會在最後一秒把請求判死。
實務上,應該先根據你的流量類型決定策略。靜態資源應該儘量快取,讓 CloudFront 命中邊緣節點;動態 API 則要分清楚哪些可以快取,哪些必須直達源站。若所有請求都走同一條慢路徑,再怎麼調 timeout 都只是把問題延後。CloudFront 適合承接合理的等待,不適合替你掩蓋後端設計上的慢。
如果你的業務屬於短請求高頻率,CloudFront 的 origin keep-alive 設定很重要;如果業務屬於長處理請求,則要先確認是不是該改成非同步處理、輪詢查詢或任務化架構。很多 504 其實不是基礎設施不夠強,而是應用本身不適合用同步請求硬撐。
Alpine 容器裡最容易被忽略的兩個細節
檔案描述符與連線上限
容器環境常見的問題之一,是檔案描述符太少。平常請求不多時感覺不到,一旦並發上來,Nginx 和上游應用就開始排隊,排隊時間一長,CloudFront 先超時,最後你看到的就是 504。若日誌中出現 Too many open files,或 worker_connections 不夠,先把系統與 Nginx 的資源上限補齊,再談其他優化。
這類問題在 Alpine 容器裡更容易被忽略,因為它太輕了,很多人只關心鏡像大小,忘了連線能力也是成本。連線數上不去,keepalive 就沒有意義;連線能建立卻不能穩定維持,CloudFront 只會更快放棄。
優雅下線與滾動部署
如果你在部署時特別容易看到 504,先不要怪流量。很多情況是容器正在重啟或切換,舊連線還沒處理完,新連線又進來了,結果 Nginx 或上游應用在中途把請求切斷。CloudFront 對這種情況非常敏感,因為它看見的是請求剛送出去就沒下文,最後自然判成超時。
正確做法是讓應用與 Nginx 支援優雅停機,讓負載均衡有足夠時間把流量移開,並且在容器層設好合理的 termination grace period。若你的服務需要幾秒鐘關閉連線,就不要讓平台三秒內強殺,否則 504 會在部署時特別多。
實戰排查順序,照著做比較不會亂
- 先直連源站,確認問題是否只在 CloudFront 後面出現。
- 看 Nginx error log,抓出第一個真正的失敗點。
- 比對 access log 的 request_time 與 upstream_response_time,判斷是前端等待還是後端執行慢。
- 檢查 Nginx 是否使用 HTTP/1.1,Connection header 是否正確。
- 確認 keepalive_timeout、proxy_read_timeout、proxy_send_timeout 與 CloudFront 的 origin timeout 是否一致。
- 檢查容器資源:CPU、記憶體、檔案描述符、連線數與重啟次數。
- 最後再回頭看應用與資料庫,避免一開始就把鍋丟給 CloudFront。
幾個常見但沒什麼用的做法
第一個是單純把 timeout 調很大,認為這樣就能治好 504。這種做法只會把錯誤變慢,不會把錯誤消除。如果後端真的慢,等待時間變長只會讓更多連線卡住,整體吞吐更差。
第二個是只改一邊設定。CloudFront 改了,Nginx 沒改;Nginx 改了,上游應用還是秒斷;表面上所有人都動過,實際上整條鏈路還是不一致。真正有效的修復,一定是同步看邊緣、入口與後端。
第三個是把 504 當成單一元兇。很多時候它只是結果,不是原因。慢查詢、DNS 波動、容器重啟、CPU 抢占、GC 暫停、連線池耗盡,任何一個環節出問題,都可能把 CloudFront 推到 504。你要修的是上游秩序,不是只修最後顯示錯誤的那一層。
結語:把責任邊界切清楚,504 才會少
CloudFront 報 504 時,最怕的不是錯,而是誤判。誤以為是 AWS 的問題,結果一直在邊緣層打轉;誤以為是 Nginx 的問題,結果上游應用早就慢到不行;誤以為是 timeout 太短,結果真正的問題是連線模式根本沒對上。只要把請求路徑拆開,看清楚每一層到底在等什麼、什麼時候斷、為什麼斷,排查速度就會快很多。
對 Alpine/Nginx 這種精簡型源站來說,最重要的不是堆更多設定,而是讓 CloudFront、Nginx 和後端服務使用相同的節奏。該複用的連線就複用,該等待的時間就一致,該快取的內容就不要硬打到源站。當每一層都各司其職,504 會少很多,定位問題也會簡單很多。

