冒頭まとめ

AWS の 504 Gateway Timeout は、リクエストを中継するコンポーネントが、その先からの応答を待ちきれずに打ち切ったことを示します。対処を決めるのは「どの中継役の、どのタイマーで時間切れになったか」です。代表的な発生源は3つです。第一に Application Load Balancer(ALB)で、ターゲットへの接続確立が10秒の接続タイムアウト内に完了しない場合(経路の遮断が典型)と、接続後の応答が idle timeout(既定60秒)内に返らない場合(遅い処理が典型)があります。第二に Classic Load Balancer で、応答が idle timeout を超える場合と、バックエンドの keep-alive が先に切れて接続が閉じられる場合です。第三に API Gateway で、統合タイムアウト(既定29秒)内にバックエンドが応答しない場合に、Endpoint request timed out という504が返ります。

504 は「待った末の時間切れ」であり、接続自体の失敗や不正な応答(502)、ターゲットの未登録(503)とは原因の場所が異なります。まずどのタイマーかを特定し、遅い側を速くするか、待つ側の設定を実態に合わせるかを判断します。

エラーの概要

504 を最初に受け取ったとき、502 との違いを押さえておくと迷いません。502 は「相手から不正な応答を受け取った、または接続に失敗した」、504 は「応答を待ったが時間内に届かなかった」です。同じ「バックエンドの問題」でも、502 は接続や応答の形式、504 は時間が争点になります。

ALB では、504 の記録の形に特徴があります。実際の調査記録に共通するのは、アクセスログで elb_status_code が 504、target_processing_time と response_processing_time が -1、target_status_code が -(空)という形です。これは、ALB がターゲットからの応答を一度も受け取れないままタイムアウトで接続を閉じたことを意味します。API Gateway の場合は、応答本文そのものが手がかりです。

{
  "message": "Endpoint request timed out"
}

まず最初に:どのタイマーで時間切れになったかを特定する

発生源ごとに、見るべき記録が決まっています。

ALB 経由なら、CloudWatch の HTTPCode_ELB_504_Count(ALB が生成した504)を確認し、あわせて TargetConnectionErrorCount(接続確立の失敗)と TargetResponseTime(ターゲットの応答時間)を見ます。接続エラーが増えていれば経路の問題(原因1の接続確立側)、応答時間が idle timeout に張り付いていれば処理の遅さ(原因1の応答待ち側)です。

API Gateway 経由なら、IntegrationLatency メトリクス(アクセスログでは $context.integrationLatency)と、バックエンドが Lambda なら関数の Duration を比べます。統合の所要時間がタイムアウト値に達していれば、時間切れの場所は統合先です(原因3)。

よくある原因と解決手順

原因1:ALB のタイムアウト(接続確立10秒・応答待ち idle timeout)

公式の情報源が ALB の504の原因として挙げているのは次のとおりです。ターゲットへの接続確立が10秒の接続タイムアウト内に完了しない。接続はできたが、ターゲットが idle timeout 内に応答しない。ターゲットが実際の本文より大きい Content-Length ヘッダーを返し、残りを待って時間切れになる。ターゲットが Lambda 関数で、応答が間に合わない。そして調整できない10秒の SSL ハンドシェイクタイムアウト、です。

接続確立側の典型は経路の遮断です。ターゲットのセキュリティグループが ALB からの通信を許可していない、またはサブネットのネットワーク ACL が、ターゲットから ALB へ戻る通信(エフェメラルポート 1024〜65535)を許可していない場合、接続が確立できず504になります。ヘルスチェックも同時に失敗しているなら、この系統がまず疑わしい状態です。

応答待ち側の典型は処理の遅さです。特定の操作だけが504になる、データ量の多い利用者だけが504になる、という偏りが出ます。対処の本筋は遅い処理の改善(アプリケーションの効率化、EC2 のインスタンスサイズや ECS タスクの CPU・メモリ、Lambda のメモリの増強)です。大きなファイルの処理など、正当に時間のかかる操作がある場合は、idle timeout を実態に合わせて延ばします。

# 現在の idle timeout を確認
aws elbv2 describe-load-balancer-attributes \
  --load-balancer-arn <ロードバランサーのARN> \
  --query 'Attributes[?Key==`idle_timeout.timeout_seconds`]'

原因2:Classic Load Balancer のタイムアウトと keep-alive の不整合

Classic Load Balancer について、公式ドキュメントは2つの原因を挙げています。1つ目は、アプリケーションの応答が idle timeout より長くかかることです。対処は、処理能力の増強か、大きなファイルアップロードのような長い操作が完了できるよう idle timeout を延ばすことです。2つ目は、登録インスタンス側が接続を先に閉じてしまうことです。公式の対処は、EC2 インスタンスで keep-alive を有効にし、keep-alive のタイムアウトロードバランサーの idle timeout より長く設定することです。バックエンドの Web サーバー側の設定(keep-alive の秒数)とロードバランサー側の設定の大小関係が逆転していると、正常な負荷でも断続的な504が出ます。

原因3:API Gateway の統合タイムアウト

API Gateway は、統合先(Lambda や HTTP バックエンド)が統合タイムアウト内に応答しない場合に504を返します。既定値は29秒です。IntegrationLatency と Lambda の Duration で時間切れの所在を確認したうえで、対処は3方向あります。

第一に、バックエンドの処理を短縮することです。応答に必要な処理だけを同期部分に残します。第二に、29秒を超えることが正当な長時間処理は、同期応答をやめて非同期の構成(先に受付だけ応答し、処理は Lambda の非同期呼び出しや SQS 経由で行う)に切り替えることです。公式ナレッジセンターも長時間処理にはこの構成を案内しています。第三に、Regional またはプライベートの REST API であれば、Service Quotas で Maximum integration timeout in milliseconds の引き上げを申請し、統合タイムアウト自体を29秒より長くできます。承認後に各 API の統合設定で値を変更し、再デプロイが必要です。なお、この引き上げはアカウントのスロットル上限の引き下げを伴う場合があり、HTTP API・WebSocket API・エッジ最適化 REST API は対象外です。以前は29秒が固定の上限だったため、古い記事の記述と現在の仕様が異なる点に注意してください。

補足:504ではない類似エラー

接続の失敗や不正な応答は 502 です(AWS の 502 エラー)。ターゲットグループにターゲットが登録されていない場合は 503 です(AWS の 503 エラー)。また、CloudFront などさらに手前の中継役を挟んでいる場合、手前のタイマーのほうが短ければ504を返すのは手前側になります。多段構成では、外側ほどタイマーが長くなる(クライアント > CloudFront > ALB > アプリ内の処理タイムアウト)ように整合させると、時間切れの発生場所が安定し、調査が楽になります。

切り分けの順序

  1. どの中継役が504を返したかを確定する(ALB のメトリクス・アクセスログか、API Gateway のログか、応答本文の文言か)。
  2. ALB なら、TargetConnectionErrorCount と TargetResponseTime で「接続できていない」のか「応答が遅い」のかを分ける。前者はセキュリティグループとネットワーク ACL(戻りのエフェメラルポート含む)、後者は遅い処理の特定と改善、必要なら idle timeout の調整。
  3. Classic Load Balancer なら、idle timeout と処理時間の関係、そしてバックエンドの keep-alive 設定がロードバランサーの idle timeout より長いかを確認する。
  4. API Gateway なら、IntegrationLatency とバックエンドの実行時間を確認し、処理短縮・非同期化・(REST API なら)統合タイムアウトの引き上げから選ぶ。

確認コマンド集

# 1. ALB が生成した504と接続エラーの件数を確認
aws cloudwatch get-metric-statistics \
  --namespace AWS/ApplicationELB \
  --metric-name HTTPCode_ELB_504_Count \
  --dimensions Name=LoadBalancer,Value=<ロードバランサーの識別子> \
  --start-time <開始時刻> --end-time <終了時刻> \
  --period 300 --statistics Sum

# 2. ALB の idle timeout の現在値を確認
aws elbv2 describe-load-balancer-attributes \
  --load-balancer-arn <ロードバランサーのARN> \
  --query 'Attributes[?Key==`idle_timeout.timeout_seconds`]'

# 3. ターゲットの健康状態を確認(接続系の問題はここにも現れる)
aws elbv2 describe-target-health --target-group-arn <ターゲットグループのARN>

# 4. 応答時間を実測して、どの秒数で切られているかを確認
curl -o /dev/null -s -w "HTTP:%{http_code} 合計:%{time_total}s\n" http://<対象のURL>/

Editor’s Note

原因1(接続確立側)の実例として、AWS 公式フォーラム re:Post の投稿があります(504 Gateway Time-Out when launching load balancer?、2021年)。ロードバランサー経由のアクセスだけが 504 Gateway Time-Out になり、インスタンスへ直接アクセスすると正常に応答するという報告です。回答では、ALB の504の原因一覧(接続確立の10秒タイムアウト、idle timeout 超過、Content-Length の過大)が示されたうえで、報告者の環境ではターゲットグループのヘルスチェックも失敗していることから、ロードバランサーからバックエンドへの接続自体が確立できていない、つまり経路(セキュリティグループなどの設定)の問題であると切り分けられています。「直接なら動くのに経由すると504」という症状が、処理の遅さではなく接続経路を指しているという、切り分けの典型例です。やや古い投稿ですが、ここで示されている ALB の504の原因一覧は、現在の公式情報源の記載と一致しています。

504 の調査は、時間切れを起こしたタイマーの特定がすべてです。どの中継役の、接続と応答のどちらの待ち時間かが分かれば、対処は経路の修復・処理の改善・タイマーの整合のどれかに自然に決まります。


免責事項:本記事の内容は、執筆時点の公開情報をもとに作成したものです。ソフトウェアの仕様は予告なく変更されることがあります。最新の情報は各ツールの公式サポートページをご確認ください。本記事の情報を利用した結果生じたいかなる損害についても、著者および運営者は責任を負いかねます。