結論

503 Service Unavailable は「サーバーは生きているが、過負荷かメンテナンスで今は処理できない」という一時的な状態です。見ている人は少し待って再読み込みし、作る側は Retry-After で待ち時間を伝え、クライアントはその時間を守って再送します。

この記事でわかること

  • 503の原因9種と、見る人・運営者別の直し方(nginx の error.log 原文つき)
  • nginx・Express・FastAPI で「いつ503を返すか」を実装した curl -v の原文
  • Retry-After を守るクライアントのログと、再送の待ち方で503が5016件→37件に減った実測

2026年10月29日更新|nginx 1.24・Express 5.3・FastAPI 0.162・Chromium 153で実行確認

503は「断っている」のではなく「今は無理なので、あとでもう一度」という意味です。サーバーを作る側は、次の順で判断すると返すコードがぶれません(503・429 には Retry-After を付けます)。

flowchart TD
    R[リクエスト] --> M{メンテ?}
    M -->|はい| S1[503]
    M -->|いいえ| L{1人だけ送りすぎ?}
    L -->|はい| T[429]
    L -->|いいえ| C{空きが無い?}
    C -->|はい| S2[503]
    C -->|いいえ| OK[処理する]

よく調べられることの答えを先にまとめます。

知りたいこと 答え 詳しい節
503エラーとは 過負荷かメンテナンスで一時的に処理できない(RFC 9110) 503 Service Unavailableとは
503 service unavailable が出た(見る人) 数分待って再読み込み。他の端末・回線でも出るか見る 見ている人の対処
503の原因(運営者) アクセス集中・メンテナンス・振り分け先なし・レート制限 503エラーの原因一覧
nginx が503を返す設定 limit_req・limit_conn の既定が503。メンテナンスは return 503 nginxで503になる設定
retry-after とは 再送まで待つ時間。秒か HTTP 日付の2形式 Retry-Afterヘッダーの書き方
いつ503を返すか(作る人) メンテナンス・処理の空きなし・停止準備中。相手1人の送りすぎは429 アプリで過負荷時に503を返す実装
503を受け取ったらどう処理するか Retry-After を守る→無ければ指数バックオフ+ジッター。上限を決める クライアント側の503の扱い
502・504との違い 502は奥からの応答が不正、504は奥が時間内に返さない 429・502・503・504の使い分け
503は SEO に影響するか 短期間なら問題にならない。数日以上は索引から外れる 503とSEO

確認環境:Ubuntu 24.04.5 LTS の上で、nginx 1.24.0(Ubuntu)を自分の設定ファイルで 127.0.0.1 のポートに起動し、メンテナンスのフラグ・limit_req・limit_conn・upstream の振り分け先なしで503と502を返しました。奥のアプリは Express 5.3.0(Node.js 22.23.4)と FastAPI 0.162.0(Starlette 1.10.0・uvicorn 0.54.0・Python 3.13.18)で、同時処理の上限を超えたら 503+Retry-After を返す形に作っています。応答は curl 8.5.0 の curl -v、ブラウザの表示は Chromium 153.0.8010.12(playwright 1.63.0・日本語の画面)で確かめました。クライアントの再送は Python 3.13 の標準ライブラリ・requests 2.34.2+urllib3 2.8.0・Node.js 22 の fetch・curl の --retry で実行しています。503と Retry-After の定義は RFC 9110(HTTP Working Group が公開している RFC のソース)、検索への影響は Google 検索セントラル、ALB は AWS、readiness は Kubernetes と Spring Boot の公式ドキュメントで確かめました。Spring の例は公式ドキュメントと Javadoc で確かめた範囲で書いています。図は mermaid で描画を確かめています(確認日 2026年10月29日)。

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「503エラーとは|Service Unavailableの原因・直し方・返し方」

503 Service Unavailableとは(意味と仕様)

この節では、503の正確な意味と、仕様(RFC 9110)がサーバーに何を求めているかが分かります。

503 Service Unavailable は、一時的な過負荷か予定されたメンテナンスのために、サーバーが今はリクエストを処理できないことを示すステータスコード(サーバーが返す3桁の結果番号)です。 RFC 9110 の 15.6.4 は次のように定義しています(訳は筆者)。

503(Service Unavailable)は、一時的な過負荷または予定されたメンテナンスのために、サーバーが現在リクエストを処理できないことを示す。この状態はしばらくすれば解消される見込みである。サーバーは、クライアントが再試行するまでに待つのが適切な時間を示すために、Retry-After ヘッダーフィールドを送ってもよい(MAY)。

注:503が存在するからといって、過負荷になったサーバーがそれを使わなければならないわけではない。単に接続を拒否するサーバーもある。

ここから3つのことが読み取れます。

1一時的な状態である

「しばらくすれば解消される見込み」が前提。永続的に提供しないものに503は使わない(その場合は404・410など)。

2Retry-After は任意(MAY)

付けなくても仕様違反ではないが、付ければクライアントが「いつ再送すればよいか」を迷わない。

3サーバーは生きている

503を返せるということは、HTTP を話せる何かが応答している。接続すらできない(タイムアウト・接続拒否)のとは別の状態。

MDN は、過負荷の場合に「メモリー、CPU、または接続プールの制限などのリソースのしきい値に達すると、サーバーサイドのアプリケーションの一部が 503 ステータスでリクエストを拒否します。リクエストを拒否することで、サーバーのコンピューターリソースが枯渇するのを防ぐ背圧が生じ、より深刻な失敗を避けることができます」と説明しています。503は「失敗」ではなく、サーバーを守るための「断り方」でもあります。

RFC 9110 で「発見的にキャッシュしてよい(heuristically cacheable)」と定義されているのは 200・203・204・206・300・301・308・404・405・410・414・501 で、503は含まれていません。MDN も、503は一時的な課題を示すためレスポンスは通常キャッシュされるべきではない、修正の展開後にクライアントが前のエラーページを受け取る可能性がある、と注意しています。手元の実装では Cache-Control: no-store を一緒に付けました。

503エラーの原因一覧(アクセス集中・メンテナンス・振り分け先なし)

この節では、503になる原因を、どの層が返しているかで分けて一覧で見られます。

503は、アプリだけでなく、Web サーバー・ロードバランサー・CDN など「手前の層」が返すことが多いコードです。 どの層が返したかで直し方が変わります。

原因 返している層 手元で確かめた
アクセス集中で処理の空きが無い(ワーカー・接続プールの枯渇) アプリ・アプリサーバー Express・FastAPI
メンテナンスモード(意図して返す) Web サーバー・アプリ nginx
レート制限・同時接続の制限に当たった Web サーバー・WAF nginx の limit_req・limit_conn
振り分け先が1台も無い(全滅・登録なし) ロードバランサー nginx は502、ALB は503(公式)
起動直後・停止準備中で readiness が落ちている アプリ(ヘルスチェック) Express・FastAPI
奥のアプリが503を返し、そのまま中継した Web サーバー(中継) nginx
クラウド側のメンテナンス・容量不足 クラウド基盤 公式ドキュメント
WordPress の更新中(.maintenance ファイル)・プラグインの不具合 アプリ ―(切り分け方だけ)
DDoS などの攻撃を受けて処理が追いつかない 全体 ―(対処は WAF・CDN)

手元で nginx の設定だけで503になったのは、メンテナンスのフラグ(return 503)・limit_req・limit_conn の3つでした。upstream(奥のサーバー群)が全滅したときは503ではなく502になりました(後述)。

503エラーが出たときの対処(見ている人向け)

この節では、Web サイトを見ていて「503 Service Unavailable」が出たときに、自分でできることと、できないことが分かります。

503は、ほとんどの場合サイト側の問題で、見ている人ができるのは「少し待つ」「自分だけかを確かめる」です。 何度も連打すると、混雑しているサーバーをさらに混ませます。

1

1〜5分待ってから再読み込みする

アクセス集中なら数分で収まることが多い。チケット販売・セール開始・テレビ放送直後は、しばらく時間を置く。連打は避ける。

すべての503

2

画面の文言を読む

「メンテナンス中」「○時に再開」と書いてあれば、その時刻まで待つ。「nginx」「cloudflare」などソフトの名前だけの画面なら、サイト側の過負荷か設定。

メンテナンス

3

別の端末・別の回線で開く

スマホのモバイル回線と Wi-Fi を切り替える。全員に出ているなら待つしかない。自分の回線だけなら、IP アドレスごとのレート制限や WAF に当たっている可能性がある。

自分だけ503

4

ブラウザの拡張機能・VPN を切る

短時間に大量のリクエストを送る拡張機能(先読み・スクレイピング系)や、共有 IP の VPN は、レート制限に当たりやすい。シークレットウィンドウで開いて変わるかを見る。

自分だけ503

5

決済・送信の直後なら、再送信しない

注文や申し込みを送った直後の503は、処理が終わってから503になった可能性がある。完了メール・購入履歴を確かめてから、もう一度送る。

フォーム・決済

Chromeでの503の見え方(HTTP ERROR 503)

手元で3通りの503を返し、Chromium(日本語の画面)で開いたときの表示です。

返した503 ブラウザに見えた表示
本文の無い503 このページは動作していません/127.0.0.1 では現在このリクエストを処理できません。/HTTP ERROR 503/再読み込み
nginx の既定の503 503 Service Temporarily Unavailable/nginx/1.24.0 (Ubuntu)
自作のメンテナンス画面(error_page で差し替え) ただいまメンテナンス中です/2026年10月29日 23:30 ごろ再開の予定です。…

「HTTP ERROR 503」の画面は、サーバーが本文の無い503を返したときにブラウザが代わりに出す画面です。 本文があるときは、サーバーが返した HTML がそのまま表示されました。画面の下に nginx などソフトの名前があれば、そのソフトが返した503です。

503エラーの直し方(運営者・開発者向けの切り分け)

この節では、自分のサイトや API が503を返しているときに、どの層が返したかを突き止める順番が分かります。

「どの層が返したか」を最初に決めると、見る場所が1つに絞れます。 応答のヘッダーとログを、手前の層から順に見ます。

  • 1curl -v で Server ヘッダーと本文を見るnginx の既定の本文なら nginx、JSON ならアプリ、CDN の名前があれば CDN が返している
  • 2Web サーバーの error.log を見るnginx は limiting requests・limiting connections・no live upstreams(これは502)の文言で原因が分かる
  • 3ロードバランサーの指標を見るALB は HTTPCode_ELB_5XX_Count(LB 自身が返した)と HTTPCode_Target_5XX_Count(奥が返した)が分かれている(AWS 公式)
  • 4アプリのログと同時処理数を見る手元の Express は 503 inflight=2 のように、断った時点の同時処理数を残した
  • 5CPU・メモリ・接続数を見るtop・free・ss -s。アプリが503を返す前に接続自体が詰まっていないか
  • 6メンテナンスの残骸が無いかnginx のフラグファイル、WordPress の .maintenance ファイルが残ると503が続く

よくあるエラー/つまずき(原文つき)

見えたもの(原文) 意味 直し方
limiting requests, excess: 3.896 by zone "req1"(nginx error.log) limit_req の rate と burst を超えた rate・burst を見直す。相手1人の問題なら limit_req_status 429
limiting connections by zone "conn1"(nginx error.log) limit_conn の同時接続数を超えた 上限を上げるか、重い処理を分ける
no live upstreams while connecting to upstream(nginx error.log) 振り分け先が全部「一時的に使用不可」。クライアントには502 奥のアプリを起動し直す。max_fails・fail_timeout を見直す
upstream server temporarily disabled while reading response header 奥が503を返し、proxy_next_upstream http_503 で失敗と数えられた 503を失敗と数えてよいかを設計で決める
HTTP/1.1 503 Service Temporarily Unavailable(nginx) nginx が返す503の理由句。意味は503と同じ ログで原因を見る
curl: (7) Failed to connect to 127.0.0.1 port 45711 503ではなく接続拒否。サーバーが聞いていない プロセスの起動・ポートを確かめる
メンテナンス画面が出るが、フラグを消しても直らない ブラウザか CDN が503をキャッシュした 503に Cache-Control: no-store を付ける。CDN のキャッシュを消す
Retry-After を書いたのに応答に付かない(nginx) add_header は既定で 2xx・3xx にしか付かない add_header Retry-After 600 always;

nginxで503になる設定と直し方(limit_req・limit_conn・upstream)

この節では、nginx が自分で503を返す設定と、error.log に出る文言、upstream が全滅したときに何が返るかが、実行した結果で分かります。

nginx 1.24.0 を次の設定で 127.0.0.1:45701 に起動しました(ログの場所などの行を省いています)。

limit_req_zone  $binary_remote_addr zone=req1:1m rate=2r/s;
limit_conn_zone $binary_remote_addr zone=conn1:1m;

upstream app {
    server 127.0.0.1:45711 max_fails=1 fail_timeout=10s;   # Express
    server 127.0.0.1:45712 max_fails=1 fail_timeout=10s;   # FastAPI
}

server {
    listen 127.0.0.1:45701;
    root /path/to/html;

    if (-f /path/to/maintenance.flag) {   # 1. メンテナンス:フラグがあれば全部 503
        return 503;
    }
    error_page 503 @maintenance;
    location @maintenance {
        add_header Retry-After 600 always;
        add_header Cache-Control "no-store" always;
        rewrite ^ /503.html break;
    }
    location /search/ {                   # 2. レート制限:既定で 503
        limit_req zone=req1 burst=3;
        alias /path/to/html/;
    }
    location /api429/ {                   # 2b. 相手1人の送りすぎは 429 に変える
        limit_req zone=req1 burst=3;
        limit_req_status 429;
        alias /path/to/html/;
    }
    location /download/ {                 # 3. 同時接続の制限:既定で 503
        limit_conn conn1 1;
        proxy_pass http://127.0.0.1:45713/slow;
    }
    location /app/ {                      # 4. 中継(既定:error timeout だけ次へ)
        proxy_pass http://app/;
    }
    location /app-next/ {                 # 4b. 奥の 503 も次のサーバーへ
        proxy_next_upstream error timeout http_503;
        proxy_pass http://app/;
    }
}

limit_req・limit_conn の既定は503

nginx の公式ドキュメントでは、limit_req_status と limit_conn_status の既定はどちらも 503 です。/search/ に8本同時に送った結果です。

$ for i in 1 2 3 4 5 6 7 8; do curl -si http://127.0.0.1:45701/search/ > out/s$i.txt & done; wait
      4 HTTP/1.1 200 OK
      4 HTTP/1.1 503 Service Temporarily Unavailable

$ grep -l 503 out/s*.txt | head -1 | xargs cat
HTTP/1.1 503 Service Temporarily Unavailable
Server: nginx/1.24.0 (Ubuntu)
Content-Type: text/html
Content-Length: 288
Retry-After: 600
Cache-Control: no-store

<!doctype html>
<html lang="ja"><head><meta charset="utf-8"><title>メンテナンス中</title></head>
<body><h1>ただいまメンテナンス中です</h1>...

error.log には、断った1本ごとに次の行が残りました。excess は、許した量をどれだけ超えたかです。

2026/10/29 17:35:38 [error] 9151#9151: *43 limiting requests, excess: 3.896 by zone "req1", client: 127.0.0.1, server: , request: "GET /search/ HTTP/1.1", host: "127.0.0.1:45701"
2026/10/29 17:35:21 [error] 8230#8230: *37 limiting connections by zone "conn1", client: 127.0.0.1, server: , request: "GET /download/ HTTP/1.1", host: "127.0.0.1:45701"

ここで気づくことがあります。レート制限に当たっただけの人にも、server 全体の error_page 503 が効いて「メンテナンス中・600秒待て」の画面が返りました。 メンテナンス用の error_page 503 を server 全体に書くと、レート制限の503まで同じ画面になります。手元では、レート制限の location を limit_req_status 429 にして分けました。

$ for i in 1 2 3 4 5 6 7 8; do curl -si http://127.0.0.1:45701/api429/ > out/a$i.txt & done; wait
HTTP/1.1 429 Too Many Requests
Server: nginx/1.24.0 (Ubuntu)

127.0.0.1 - - [29/Oct/2026:17:36:25 +0900] "GET /api429/ HTTP/1.1" 429 178 "-" "curl/8.5.0"

limit_conn は、同時接続1に対して3本同時に送ると、2本が即座に503、1本が3秒後に200でした。

$ for i in 1 2 3; do curl -s -o /dev/null -w "%{http_code} %{time_total}s\n" http://127.0.0.1:45701/download/ & done; wait
503 0.001680s
503 0.000267s
200 3.001312s

upstream が全滅すると502、奥の503はそのまま中継

奥の FastAPI を停止準備中(readiness が503)にして、2台に振り分ける /app/ と /app-next/ に4本ずつ送った結果です。

$ for i in 1 2 3 4; do curl -s -o /dev/null -w '%{http_code} ' http://127.0.0.1:45701/app/; done
200 503 200 503          # 既定(proxy_next_upstream error timeout):503 はそのまま返る
$ for i in 1 2 3 4; do curl -s -o /dev/null -w '%{http_code} ' http://127.0.0.1:45701/app-next/; done
200 200 200 200          # http_503 を足した:503 を返した台を避けて次の台へ

次に Express も止めて、2台とも応答しない状態にしました。

$ for i in 1 2 3; do curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:45701/app-next/; done
502
502
502

error.log:
[warn]  *128 upstream server temporarily disabled while connecting to upstream, ... upstream: "http://127.0.0.1:45711/"
[error] *131 no live upstreams while connecting to upstream, client: 127.0.0.1, ... upstream: "http://app/"
状態 クライアントに返ったコード error.log
奥の1台が503(既定の設定) 200 と 503 が交互 なし
奥の1台が503(http_503 を追加) すべて 200 upstream server temporarily disabled
奥が全滅(接続拒否) 502 no live upstreams
limit_req・limit_conn を超えた 503(既定) limiting requests/limiting connections
メンテナンスのフラグあり 503+Retry-After なし(access.log だけ)

nginx の中継で振り分け先が全滅すると、返るのは503ではなく502です。 公式ドキュメントも、サーバーを選べないときは $upstream_status に 502 が入る、と書いています。「nginx の502」で探す人が多い理由です。proxy_next_upstream は既定が error timeout で、奥の503はそのまま返ります。http_503 を足すと次の台に送り直しますが、POST などの冪等でないメソッドは、送信済みなら既定では次の台へ送らない(non_idempotent で明示的に許可)と公式ドキュメントにあります。奥の503を nginx の画面に差し替えたいときは proxy_intercept_errors on; と error_page 503 を組み合わせます。中継の設定の全体は、関連記事「Nginxリバースプロキシ設定|proxy_passの書き方と502/504対処」で扱っています。

メンテナンス時に503を返す方法(nginxとRetry-After)

この節では、メンテナンスのときに200の「お知らせページ」ではなく503を返すべき理由と、nginx での実装が分かります。

メンテナンス中は、200ではなく503でお知らせページを返します。 200で返すと、検索エンジンは「お知らせページ」を正式な内容として索引に登録し、クライアントやキャッシュも「成功した」と扱います。Google 検索セントラルは、サイトを1〜2日ほど緊急に止める場合は「コンテンツの代わりに 503 HTTP レスポンス ステータス コードを含む情報エラーページを返す」こと、retry-after ヘッダーで再訪の目安(日付か期間)を伝えること、そして「robots.txt ファイルには 503 を返さない(すべてのクロールがブロックされるため)」ことを書いています。

手元の nginx では、フラグのファイルの有無で切り替える形にしました。デプロイ前に touch maintenance.flag、終わったら消します。再起動もリロードも要りません。

$ touch maintenance.flag
$ curl -v http://127.0.0.1:45701/
> GET / HTTP/1.1
> Host: 127.0.0.1:45701
>
< HTTP/1.1 503 Service Temporarily Unavailable
< Server: nginx/1.24.0 (Ubuntu)
< Content-Type: text/html
< Content-Length: 288
< Retry-After: 600
< Cache-Control: no-store
<
<!doctype html>
<html lang="ja"><head><meta charset="utf-8"><title>メンテナンス中</title></head>
<body><h1>ただいまメンテナンス中です</h1><p>2026年10月29日 23:30 ごろ再開の予定です。しばらくしてから再度お試しください。</p></body></html>

$ rm maintenance.flag
$ curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:45701/
200

add_header は always を付けないと503に付かない

nginx の add_header は、公式ドキュメントのとおり、既定では応答コードが 200・201・204・206・301・302・303・304・307・308 のときだけヘッダーを足します。always を付けると、応答コードに関係なく足されます。手元で比べた結果です。

location = /no-always   { add_header Retry-After 120;        return 503 "..."; }
location = /with-always { add_header Retry-After 120 always; return 503 "..."; }
$ curl -si http://127.0.0.1:45701/no-always | grep -i 'HTTP/\|retry'
HTTP/1.1 503 Service Temporarily Unavailable
                                   ← Retry-After が付かない
$ curl -si http://127.0.0.1:45701/with-always | grep -i 'HTTP/\|retry'
HTTP/1.1 503 Service Temporarily Unavailable
Retry-After: 120

メンテナンスページの返し方

やってはいけない全 URL を「メンテナンス中」の HTML に 200 で返す。302 でお知らせページへ転送する。robots.txt まで 503 にする
→
代わりに503+Retry-After+Cache-Control: no-store で、再開の予定を書いた HTML を返す。robots.txt は 200 のまま。数日以内に終える

Google は、503 で止めるのは「非常に短い期間(長くても2〜3日)」の最終手段としている

Retry-Afterヘッダーの書き方(秒とHTTP日付の2形式)

この節では、Retry-After に何を書くか、2つの形式の違い、誰が守るのかが分かります。

Retry-After は、クライアントが次のリクエストを送るまでに待つべき時間を伝える応答ヘッダーで、値は「秒数」か「HTTP 日付」のどちらかです。 RFC 9110 の 10.2.3 は次のように定めています。

Retry-After = HTTP-date / delay-seconds
delay-seconds = 1*DIGIT          # 0 以上の 10 進整数(秒)

Retry-After: Fri, 31 Dec 1999 23:59:59 GMT
Retry-After: 120                 # 2 分
秒数(delay-seconds) HTTP 日付(HTTP-date)
例 Retry-After: 120 Retry-After: Thu, 29 Oct 2026 08:39:12 GMT
向いている場面 過負荷・レート制限(「少し待って」) メンテナンス(「○時に再開」)
クライアントの計算 受け取った時刻+秒数 日付−今の時刻。時計のずれに注意
実装の手間 整数を書くだけ GMT の形式で書く(Python は email.utils.formatdate(usegmt=True))
一緒に使うコード 503・429 503・3xx(転送前に待たせる最小時間)

RFC 9110 は、503と一緒に送ったときは「サービスが利用できないと見込まれる時間」、3xx と一緒に送ったときは「転送先へリクエストする前に待つ最小の時間」としています。429(Too Many Requests)での使い方は RFC 6585 で、MDN も429に Retry-After を含められると書いています。

誰が守るか:MDN は「クライアントとサーバー双方の Retry-After ヘッダーの対応は、まだ一貫していません。ただし、Googlebot のような一部のクローラやスパイダーは Retry-After ヘッダーを尊重します」と書いています。一般のブラウザは Retry-After を見て自動で再読み込みはしません。守るのは、自分が書く API クライアント・バッチ・SDK と、クローラーです。 手元では curl の --retry、Python の urllib3、自作のクライアントが守るのを確かめました(後述)。

アプリで過負荷時に503を返す実装(Express・FastAPI・Spring)

この節では、アプリが「いつ503を返すか」を決める基準と、Express・FastAPI の実装、Spring Boot での考え方が分かります。

アプリが503を返すのは、(1) 同時処理の空きが無いとき、(2) 停止準備中(graceful shutdown)、(3) 起動直後で準備ができていないとき、の3つです。 特定のクライアントだけが送りすぎているなら、それは503ではなく429です。

  1. 1同時処理数が上限 → 503+Retry-After: 1〜数秒待たせずに即断る。待たせると接続が溜まって全員が遅くなる
  2. 2停止準備中 → /healthz が503、新しい処理も503ロードバランサーが振り分けから外すのを待ってから閉じる
  3. 3起動直後・依存先の準備待ち → /healthz が503readiness が通るまで振り分けられない
  4. 4相手1人の送りすぎ → 429+Retry-Afterサーバー全体の容量の話ではないので503にしない
  5. 5奥の API が落ちている → 502/504(または503)自分が中継役なら502・504。自分の機能として提供できないなら503

Express 5:同時処理数で断る

同時処理数(inflight)が上限なら、処理に入らず即座に503を返すミドルウェアです。Retry-After・Cache-Control: no-store・本文は RFC 9457(Problem Details)の形にしています。

const MAX_INFLIGHT = 2;            // 同時に処理する上限(本番は計測して決める)
let inflight = 0;
let draining = false;              // 停止準備中

function unavailable(res, retryAfter, title) {
  res.status(503)
    .set('Retry-After', String(retryAfter))        // 秒で伝える
    .set('Cache-Control', 'no-store')              // 503 をキャッシュさせない
    .type('application/problem+json')
    .json({ type: 'about:blank', title, status: 503, retry_after: retryAfter });
}

app.get('/healthz', (req, res) => {               // readiness
  if (draining) return unavailable(res, 5, '停止準備中です');
  res.json({ status: 'ok', inflight });
});

app.use((req, res, next) => {                     // 背圧:空きが無ければ即 503
  if (draining) return unavailable(res, 5, '停止準備中です');
  if (inflight >= MAX_INFLIGHT) {
    console.log(`${new Date().toISOString()} 503 inflight=${inflight} ${req.method} ${req.path}`);
    return unavailable(res, 1, 'ただいま混み合っています');
  }
  inflight++;
  res.on('finish', () => { inflight--; });
  next();
});

const server = app.listen(45711, '127.0.0.1');
process.on('SIGTERM', () => {                     // graceful shutdown
  draining = true;                                 // まず readiness を 503 に
  setTimeout(() => server.close(() => process.exit(0)), 3000);  // 外れるのを待って閉じる
});

1.5秒かかる処理に6本同時に送った結果です。2本だけ処理され、4本は数ミリ秒で503が返りました。

$ for i in 1 2 3 4 5 6; do curl -s -o /dev/null -w "%{http_code} %{time_total}s\n" "http://127.0.0.1:45711/slow?ms=1500" & done; wait
503 0.004115s
503 0.002123s
503 0.003652s
503 0.003501s
200 1.510877s
200 1.501698s

$ curl -v "http://127.0.0.1:45711/slow?ms=1500"      # 混んでいる最中に送る
< HTTP/1.1 503 Service Unavailable
< Retry-After: 1
< Cache-Control: no-store
< Content-Type: application/problem+json; charset=utf-8
<
{"type":"about:blank","title":"ただいま混み合っています","status":503,"retry_after":1}

app.log:
2026-10-29T08:36:43.006Z 503 inflight=2 GET /slow
2026-10-29T08:36:43.009Z 503 inflight=2 GET /slow

graceful shutdown:readiness を先に503にしてから閉じる

4秒かかる処理の途中で SIGTERM を送った結果です。進行中の処理は200で完了し、その間の新しいリクエストと /healthz は503、3秒後に閉じたあとは接続拒否になりました。

$ curl -s -w ' [%{http_code} %{time_total}s]\n' "http://127.0.0.1:45711/slow?ms=4000" &
$ kill -TERM <PID>
$ curl -si http://127.0.0.1:45711/healthz          # 0.3 秒後
HTTP/1.1 503 Service Unavailable
Retry-After: 5
{"type":"about:blank","title":"停止準備中です","status":503,"retry_after":5}
slow done (4000ms)
 [200 4.003086s]                                   # 進行中の処理は最後まで
$ curl -sS http://127.0.0.1:45711/healthz          # 3.8 秒後(server.close() 後)
curl: (7) Failed to connect to 127.0.0.1 port 45711 after 0 ms: Couldn't connect to server

app.log:
2026-10-29T08:37:57.847Z SIGTERM: draining, inflight=1
2026-10-29T08:38:01.353Z closed

最初に作った版は SIGTERM で即 server.close() を呼んでいて、readiness の503を返す前に新しい接続を受けなくなり、/healthz は接続拒否になりました。 ロードバランサーが「このサーバーを外す」と判断する前に閉じてしまうと、その間のリクエストが失敗します。readiness を503にしてから、振り分けから外れる時間(数秒)を待って閉じる、という順番が要ります。

FastAPI:Semaphore で空きを見る

MAX_INFLIGHT = 2
sem = asyncio.Semaphore(MAX_INFLIGHT)
state = {"draining": False}

def unavailable(retry_after, title):
    return JSONResponse(
        status_code=503,
        content={"type": "about:blank", "title": title, "status": 503},
        headers={"Retry-After": str(retry_after), "Cache-Control": "no-store"},
        media_type="application/problem+json",
    )

@app.middleware("http")
async def backpressure(request: Request, call_next):
    if request.url.path == "/healthz":
        return await call_next(request)
    if state["draining"]:
        return unavailable(5, "停止準備中です")
    if sem.locked():                     # 空きが無ければ待たずに 503
        return unavailable(1, "ただいま混み合っています")
    async with sem:
        return await call_next(request)

@app.get("/healthz")
async def healthz():
    if state["draining"]:
        return unavailable(5, "停止準備中です")
    return {"status": "ok"}
$ for i in 1 2 3 4 5 6; do curl -s -o /dev/null -w "%{http_code} %{time_total}s\n" "http://127.0.0.1:45712/slow?ms=1500" & done; wait
503 0.004465s
503 0.009438s
503 0.017500s
503 0.008084s
200 1.502689s
200 1.511107s

$ curl -si "http://127.0.0.1:45712/slow?ms=1500"
HTTP/1.1 503 Service Unavailable
server: uvicorn
retry-after: 1
cache-control: no-store
content-type: application/problem+json

{"type":"about:blank","title":"ただいま混み合っています","status":503}

HTTP 日付の形式で返すときは、Python なら email.utils.formatdate(time.time() + 3, usegmt=True) で Thu, 29 Oct 2026 08:39:12 GMT の形になります。

Spring Boot:ResponseEntity と Actuator の readiness

Spring では ResponseEntity にヘッダーを足して返します。HttpHeaders.RETRY_AFTER は Spring Framework の Javadoc に「The HTTP Retry-After header field name」として定義されています(確認日 2026年10月29日)。

@GetMapping("/report")
public ResponseEntity<?> report() {
    if (!limiter.tryAcquire()) {                       // 空きが無い
        return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE)
                .header(HttpHeaders.RETRY_AFTER, "2")   // 秒、または HTTP 日付
                .cacheControl(CacheControl.noStore())
                .body(Map.of("title", "ただいま混み合っています", "status", 503));
    }
    try { return ResponseEntity.ok(service.report()); }
    finally { limiter.release(); }
}

readiness は Spring Boot Actuator が用意しています。公式ドキュメントによると、/actuator/health/readiness と /actuator/health/liveness が Kubernetes 用の Probe として公開され、ヘルスの状態 DOWN と OUT_OF_SERVICE は既定で 503 に対応します。起動中は ReadinessState が REFUSING_TRAFFIC、startup の処理が終わると ACCEPTING_TRAFFIC になり、graceful shutdown を有効にすると停止時に再び REFUSING_TRAFFIC になって進行中の処理を完了させます。アプリが「今は混みすぎ」と判断したときは、AvailabilityChangeEvent.publish(eventPublisher, this, ReadinessState.REFUSING_TRAFFIC) で readiness を落とせます(公式ドキュメントの LivenessState の例と同じ形)。

クライアント側の503の扱い(Retry-Afterを守る・バックオフ・冪等性)

この節では、API クライアント・バッチ・SDK が503を受け取ったときに、何をどの順で判断すればよいかと、再送が成功するまでのログが分かります。

503を受け取ったクライアントは、(1) Retry-After があればその時間、無ければ指数バックオフ+ジッターで待ち、(2) 冪等なリクエストだけ、(3) 回数の上限つきで再送します。 RFC 9110 は、冪等でないメソッド(POST など)の自動再送は、意味として冪等だと分かる手段か、元のリクエストが適用されなかったと分かる手段が無い限り行うべきでない(SHOULD NOT)、自動再送の失敗をさらに自動再送すべきでない、としています。

1

Retry-After を最優先

サーバーが「いつ空くか」を知っている。秒と HTTP 日付の両方を読めるようにする。

上限(例:60秒)を超える値は打ち切るか人に知らせる

2

無ければ指数バックオフ+ジッター

0.5秒・1秒・2秒…と倍にし、乱数で散らす。全員が同時に再送する「再送の波」を防ぐ。

上限(例:30秒)を決める

3

冪等なものだけ再送

GET・PUT・DELETE は再送できる。POST は Idempotency-Key を付けたときだけ。

二重登録・二重課金を防ぐ

4

回数と合計時間に上限

5回・合計60秒など。超えたら諦めて呼び出し元にエラーを返す。

無限に待たない

5

再送するコードを決める

503・429・502・504 と接続エラーだけ。400・401・403・404・500 は再送しても同じ。

500 は原因が分からないので慎重に

6

ログに残す

何回目・何秒待った・理由(Retry-After か backoff か)を残すと、障害のあとに追える。

下のログがその形

Python(標準ライブラリ):再送が成功するまでのログ

Retry-After の2形式を読み、無ければフルジッターのバックオフで待つクライアントの中心部分です。

def parse_retry_after(value, now=None):
    """秒 or HTTP-date を「待つ秒数」に直す。無い・読めないときは None"""
    if not value:
        return None
    value = value.strip()
    if value.isdigit():                          # delay-seconds
        return int(value)
    try:                                         # HTTP-date
        when = parsedate_to_datetime(value)
        now = now or datetime.now(timezone.utc)
        return max(0.0, (when - now).total_seconds())
    except (TypeError, ValueError):
        return None

def backoff(attempt, base=0.5, cap=30.0):
    """指数バックオフ+フルジッター:0〜min(cap, base*2^attempt) の乱数"""
    return random.uniform(0, min(cap, base * (2 ** attempt)))

def request_with_retry(url, method="GET", data=None, max_retries=5, idempotency_key=None):
    for attempt in range(max_retries + 1):
        req = urllib.request.Request(url, method=method, data=data)
        if idempotency_key:
            req.add_header("Idempotency-Key", idempotency_key)
        try:
            with urllib.request.urlopen(req, timeout=5) as res:
                return res.status, res.read()
        except urllib.error.HTTPError as e:
            if e.code not in (503, 429, 502, 504):          # 再送して意味があるものだけ
                raise
            if method not in IDEMPOTENT and not idempotency_key:
                raise RuntimeError(f"{e.code}: {method} は冪等でないので自動再送しない")
            if attempt == max_retries:
                raise RuntimeError(f"{e.code}: {max_retries} 回再送しても回復しない")
            ra = parse_retry_after(e.headers.get("Retry-After"))
            wait = ra if ra is not None else backoff(attempt)
            time.sleep(wait)

起動から数秒だけ503を返す API(/flaky は Retry-After: 2、/flaky-date は HTTP 日付、/flaky-nora は Retry-After なし)に向けて動かした結果です。

$ python3 retry_client.py http://127.0.0.1:45712/flaky
  0.02s attempt 0: 503 -> 2.00s 待つ(Retry-After='2')
  2.02s attempt 1: 503 -> 2.00s 待つ(Retry-After='2')
  4.03s attempt 2: 503 -> 2.00s 待つ(Retry-After='2')
  6.03s attempt 3: 200 -> 成功
body: recovered

$ python3 retry_client.py http://127.0.0.1:45712/flaky-date
  0.02s attempt 0: 503 -> 2.74s 待つ(Retry-After='Thu, 29 Oct 2026 08:39:12 GMT')
  2.76s attempt 1: 503 -> 3.00s 待つ(Retry-After='Thu, 29 Oct 2026 08:39:15 GMT')
  5.76s attempt 2: 200 -> 成功

$ python3 retry_client.py http://127.0.0.1:45712/flaky-nora
  0.02s attempt 0: 503 -> 0.27s 待つ(backoff+jitter)
  0.30s attempt 1: 503 -> 0.35s 待つ(backoff+jitter)
  0.65s attempt 2: 503 -> 1.69s 待つ(backoff+jitter)
  2.34s attempt 3: 503 -> 1.15s 待つ(backoff+jitter)
  3.50s attempt 4: 503 -> 4.08s 待つ(backoff+jitter)
  7.58s attempt 5: 200 -> 成功

$ python3 retry_client.py http://127.0.0.1:45712/flaky POST
諦めた: 503: POST は冪等でないので自動再送しない

$ python3 retry_client.py http://127.0.0.1:45712/flaky POST order-7f3a     # Idempotency-Key つき
  0.01s attempt 0: 503 -> 2.00s 待つ(Retry-After='2')
  ...
  6.02s attempt 3: 200 -> 成功

Retry-After が無いときの待ち時間は乱数なので、4回目が3回目より短い(1.15秒)こともあります。これがジッターの効果で、複数のクライアントが同じタイミングで再送しなくなります。バックオフの決め方は、関連記事「指数バックオフとは?リトライ間隔の決め方とジッターの実装」で扱っています。

ライブラリに任せる:curl・requests(urllib3)・Node.js fetch

curl の --retry は、503のときに Retry-After を守って待ちました。 -f を付けると、503の本文を出さずに再送の経過だけ表示します。

$ curl --retry 5 -f http://127.0.0.1:45712/flaky
curl: (22) The requested URL returned error: 503
Warning: Problem : HTTP error. Will retry in 2 seconds. 5 retries left.
curl: (22) The requested URL returned error: 503
Warning: Problem : HTTP error. Will retry in 2 seconds. 4 retries left.
curl: (22) The requested URL returned error: 503
Warning: Problem : HTTP error. Will retry in 2 seconds. 3 retries left.
recovered

Python の requests は、urllib3 の Retry を HTTPAdapter に渡します。respect_retry_after_header は既定で True です。

retry = Retry(total=5, backoff_factor=0.5, backoff_jitter=0.3,
              status_forcelist=[503, 502, 504, 429],
              allowed_methods=["GET", "HEAD", "PUT", "DELETE", "OPTIONS"],   # 冪等なものだけ
              respect_retry_after_header=True)
s = requests.Session()
s.mount("http://", HTTPAdapter(max_retries=retry))
r = s.get("http://127.0.0.1:45712/flaky", timeout=5)
17:40:33,134 http://127.0.0.1:45712 "GET /flaky HTTP/1.1" 503 67
17:40:33,134 Incremented Retry for (url='/flaky'): Retry(total=4, connect=None, read=None, redirect=None, status=None)
17:40:35,134 Retry: /flaky
17:40:35,137 http://127.0.0.1:45712 "GET /flaky HTTP/1.1" 503 67
17:40:37,137 Retry: /flaky
17:40:37,139 http://127.0.0.1:45712 "GET /flaky HTTP/1.1" 200 10
4.02s -> 200 recovered

Node.js 22 の fetch には再送の機能が無いので、自分で書きます。HTTP 日付は Date.parse で読めます。

function retryAfterMs(h) {
  if (!h) return null;
  if (/^\d+$/.test(h)) return Number(h) * 1000;            // 秒
  const t = Date.parse(h);                                  // HTTP-date
  return Number.isNaN(t) ? null : Math.max(0, t - Date.now());
}
async function fetchWithRetry(url, init = {}, { maxRetries = 5, base = 500, cap = 30000 } = {}) {
  for (let attempt = 0; ; attempt++) {
    const res = await fetch(url, init);
    if (![503, 429, 502, 504].includes(res.status) || attempt === maxRetries) return res;
    const ra = retryAfterMs(res.headers.get('retry-after'));
    const wait = ra ?? Math.random() * Math.min(cap, base * 2 ** attempt);
    await res.body?.cancel();                                 // 本文を捨てて接続を返す
    await new Promise((r) => setTimeout(r, wait));
  }
}
$ node retry.mjs http://127.0.0.1:45712/flaky-date
0.08s attempt 0: 503 -> wait 2.73s (Retry-After)
2.82s attempt 1: 503 -> wait 2.99s (Retry-After)
5.81s attempt 2: 200
body: recovered

POST の再送と Idempotency-Key の設計は、関連記事「冪等性とは|APIの冪等キー実装と二重登録を防ぐリトライ設計の一覧」で扱っています。

再送の待ち方で503の数がどう変わるか(実測)

この節では、同じ過負荷でも、クライアントの待ち方によってサーバーが断る回数と全員が終わるまでの時間がどれだけ変わるかが、数字で分かります。

20本のクライアントが、同時処理2本・1本0.3秒の API を同時に呼び、全員が成功するまで再送し続ける実験です。 待ち方を4通りに変えて、送った総数・503の数・全員が終わるまでの時間を測りました(Express のアプリに対して Python のスレッド20本で実行)。

5,016件待たずに即再送したときの50320本が3秒間に5,036回送った
37件Retry-After+ジッターの503送った総数は57回
6.12秒Retry-After+ジッターで全員完了固定1秒待ちは9.34秒
待ち方 送った総数 503の数 全員が終わるまで
待たずに即再送 5,036 5,016 3.04秒
固定で1秒待つ 110 90 9.34秒
Retry-After(1秒)+0〜1秒のジッター 57 37 6.12秒
指数バックオフ+フルジッター(0.3秒・上限8秒) 85 65 7.05秒
  • 即再送は最速で終わりますが、サーバーは3秒間に5,000回以上断り続けました。 本番でこれをすると、断る処理そのものが CPU と回線を使い、他のクライアントまで巻き込みます。
  • 固定1秒待ちは、20本が1秒ごとに一斉に再送するため、毎回ほとんどが503になり(90件)、終わるのも最も遅い(9.34秒)結果でした。「全員が同じ時刻に再送する」のが問題です。
  • Retry-After にジッターを足した待ち方が、503の数も完了時間も最もよい結果でした。サーバーが知っている「空く時間」を使い、乱数で散らすからです。
  • 指数バックオフ+ジッターは、Retry-After が無いときの次善の策として、固定待ちより503が少なく、速く終わりました。

数字は手元の環境(同時2本・0.3秒・20本)のものです。本番では、Retry-After の値・ジッターの幅・クライアント数で変わりますが、「一斉に再送させない」という方向は変わりません。

ロードバランサー・Kubernetesと503(ALB・readiness・graceful shutdown)

この節では、ALB が自分で503を返す条件と、Kubernetes の readiness が503とどう結びつくかが、公式ドキュメントの範囲で分かります。

ALB(AWS Application Load Balancer)が自分で返す503は、「振り分け先(ターゲット)が1つも登録されていない、またはすべて unused の状態」のときです。 AWS の公式ドキュメント(トラブルシューティング)は、HTTP 503 の原因として次を挙げています。

  • ロードバランサーのターゲットグループに登録済みのターゲットが無い、または登録済みのターゲットがすべて unused の状態。
  • target optimizer を有効にしたターゲットグループに振り分けられ、リクエストを受けられるターゲットが無くて拒否された。

同じドキュメントは、ロードバランサー自身が返したエラーは HTTPCode_ELB_5XX_Count、ターゲット(奥のアプリ)が返したエラーは HTTPCode_Target_5XX_Count に記録される、としています。CloudWatch でどちらの指標が増えているかを見れば、ALB の503かアプリの503かが分かります。 nginx が振り分け先なしで502を返したのと違い、ALB は503です。中継役のソフトごとに「全滅」のコードが違うので、公式ドキュメントで確かめます。

Kubernetes の readiness と503

Kubernetes の公式ドキュメントは、readiness probe を「コンテナがトラフィックを受けられる状態かを判断するもの」とし、readiness probe が失敗すると EndpointSlice コントローラーが、その Pod の IP アドレスを、Pod に対応するすべての Service の EndpointSlice から外す、と説明しています。HTTP probe は「200以上400未満のコードなら成功、それ以外は失敗」です。つまり、アプリの /healthz が503を返している間、その Pod には Service 経由のリクエストが届きません。

readiness が503を返す場面

起動直後で、キャッシュの読み込みや接続の準備が終わっていない

停止準備中(SIGTERM を受けた直後)

一時的な過負荷で、新しい処理を受けたくない

liveness で503を返してはいけない場面

奥の DB や外部 API が落ちているとき(再起動しても直らない)

一時的な過負荷(再起動すると、さらに悪化する)

Spring Boot の公式も「liveness を外部のチェックに基づかせない」としている

手元の Express の graceful shutdown(前述)は、この流れをそのまま実装したものです。SIGTERM を受けたら /healthz を503にして、振り分けから外れるまで数秒待ってから server.close() を呼びます。Kubernetes では terminationGracePeriodSeconds と preStop の時間を、この「外れるまでの数秒」より長く取ります。ヘルスチェックの設計は、ヘルスチェック設計|livenessとreadinessの違いと実装例 でも扱っています。

429・502・503・504の使い分け(判断表とサーキットブレーカー)

この節では、作る側が迷いやすい4つのコードを、どの状況で返すかの判断表で見分けられます。

503と429は「誰の問題か」、503と502・504は「自分が中継役か」で分かれます。 MDN は、レート制限によって特定のクライアントからのリクエストが制限されている場合、適切なレスポンスは 429 Too Many Requests だ、と説明しています。RFC 9110 の定義では、502は「ゲートウェイまたはプロキシとして動作中に、奥のサーバーから不正な応答を受け取った」、504は「ゲートウェイまたはプロキシとして動作中に、奥のサーバーから時間内に応答を受け取れなかった」です。

状況 返すコード Retry-After クライアントの動き
サーバー全体の処理の空きが無い 503 付ける(秒) 待って再送
メンテナンス中 503 付ける(日付か秒) 待って再送。人には再開予定を見せる
停止準備中・起動準備中(readiness) 503 付ける(短い秒) ロードバランサーが振り分けから外す
特定のクライアントだけが送りすぎ 429 付ける(秒) 自分の送信量を落として再送
中継役として、奥から壊れた応答・接続拒否 502 通常付けない 少し待って再送(奥の復旧待ち)
中継役として、奥が時間内に返さない 504 通常付けない 再送は慎重に(奥で処理が続いている可能性)
自分の依存先(DB など)が落ちて機能を提供できない 503(中継役なら502・504) 付けられれば付ける 待って再送
アプリのバグ・例外 500 付けない 再送しても同じ。原因を直す

手元の結果で言えば、nginx の limit_req は既定で503ですが、それは「nginx というサーバー全体が守っている」からで、API の設計として「この利用者は上限を超えた」と伝えたいなら limit_req_status 429 のほうが意味が合います。502・504 の詳しい切り分けは、関連記事「504 Gateway Time-outの原因と直し方|502・503との違いと実例」で扱っています。

サーキットブレーカーとの関係

503を返す側と受け取る側の両方に、サーキットブレーカーが関わります。

  • 受け取る側:503が一定の割合を超えたら、ブレーカーを「開」にして、しばらく呼び出し自体をやめます。再送の上限に達するまで待つのではなく、最初から呼ばないことで、相手の回復を邪魔しません。Retry-After は「半開」にして試す時刻の目安になります。
  • 返す側:自分の依存先(DB・外部 API)へのブレーカーが「開」のとき、自分は依存先を呼ばずに即座に503+Retry-After を返せます。タイムアウトを待って504を返すより、クライアントにもサーバーにも軽い動きです。

3つの状態の実装とリトライ可否の表は、関連記事「サーキットブレーカーとは|3状態の実装とリトライ可否の一覧表」で扱っています。LLM の API では、503のほかに 429 と 529(Anthropic の過負荷)が同じ扱いになります。関連記事「LLM APIの429・529エラー対処|リトライ実装とエラー一覧」で扱っています。

503とSEO(Google検索への影響)

この節では、503を返しているページが Google の検索でどう扱われるかと、長引いたときに何が起きるかが分かります。

短時間の503は検索順位に影響しませんが、数日続くと索引から外れます。 Google 検索セントラル(HTTP ステータスコードがインデックス登録に与える影響。2026年2月4日更新)は、5xx と 429 について次のように書いています。

  • 5xx および 429 のサーバーエラーは、Google のクローラに対して一時的にクロールのペースを落とすように促す。
  • すでにインデックスに登録されている URL はインデックスに保持されるが、最終的には削除される。
  • 5xx を返す URL から受信したコンテンツはすべて無視される。
  • サーバーが 2xx を返すようになると、Google はクロール頻度を徐々に引き上げる。

robots.txt が5xx を返したときの扱いは別のページ(robots.txt の仕様)にあり、最初の12時間はサイトのクロールを止めて robots.txt の再取得を続ける、30日間は前回正常に取得した版を使う、とされています。メンテナンスで503を返すときも、robots.txt は200で返します。

503の続く時間 検索への影響(Google の記述から) やること
数分〜数時間 クロールのペースが落ちるだけ。索引はそのまま 503+Retry-After で待ってもらう
1〜2日 Google が「緊急に止めるなら503」と案内している範囲 Retry-After に再開日時。robots.txt は200
2〜3日を超える 「甚大な影響」が及ぶ(Google)。索引から外れ始める 503で止め続けない。一部機能だけでも200で返す

よくある質問

Q. 503エラーとは何ですか? A. サーバーが一時的な過負荷かメンテナンスのために、今はリクエストを処理できないことを示す HTTP のステータスコードです(RFC 9110)。しばらくすれば直る見込みの状態で、サーバー自体は動いています。

Q. 503 Service Unavailable が出ました。自分で直せますか? A. ほとんどの場合、サイト側の問題なので直せません。数分待って再読み込みし、別の回線でも出るか確かめます。自分の回線だけで出るなら、レート制限や VPN の共有 IP が原因のことがあります。

Q. 503エラーはどのくらいで直りますか? A. アクセス集中なら数分から数十分、メンテナンスなら画面か Retry-After に書かれた時刻まで、が目安です。何時間も続く場合は、サイト側で設定の残骸(メンテナンスのフラグなど)や、振り分け先の全滅が起きています。

Q. 503はどのタイミングで返すべきですか? A. メンテナンス中、同時処理の空きが無いとき、停止準備中や起動準備中で readiness が落ちているとき、の3つです。特定のクライアントだけが送りすぎているなら429、自分が中継役で奥が返さないなら502・504です。

Q. Retry-After は必ず付けるべきですか? A. RFC 9110 では任意(MAY)ですが、付けると、クライアントが待つ時間を決められ、Googlebot のようなクローラーも再訪の目安にします。秒数か HTTP 日付のどちらかで書きます。nginx の add_header は always を付けないと503に付きません。

Q. 503を受け取ったら、クライアントはどう処理すればよいですか? A. Retry-After があればその時間、無ければ指数バックオフ+ジッターで待ってから、冪等なリクエストだけを、回数の上限つきで再送します。POST は Idempotency-Key を付けたときだけ再送します。

Q. 503と502・504の違いは何ですか? A. 503は「そのサーバー自身が今は処理できない」、502は「中継役が奥のサーバーから不正な応答を受け取った」、504は「中継役が奥のサーバーから時間内に応答を受け取れなかった」です。手元の nginx では、振り分け先が全滅すると502になりました。

Q. 503と429はどう使い分けますか? A. サーバー全体の容量が足りないなら503、特定のクライアントが送りすぎているなら429です。nginx の limit_req は既定で503ですが、limit_req_status 429 で変えられます。

Q. メンテナンス中は200でお知らせページを出してはいけませんか? A. 200で返すと、検索エンジンがお知らせページを本来の内容として登録し、キャッシュや監視も「成功」と扱います。Google は、短期間なら503+Retry-After で返すよう案内しています。ただし2〜3日を超えて503で止め続けると、索引から外れます。

Q. 503はキャッシュされますか? A. RFC 9110 で「発見的にキャッシュしてよい」とされるコードに503は含まれません。ただし、Cache-Control などで明示すればキャッシュされ得るため、503には Cache-Control: no-store を付けて、復旧後に前のエラーページが出ないようにします。

Q. WordPress で503が出ます。 A. 更新中に作られる .maintenance ファイルが残っている、プラグインやテーマの処理が重い、PHP のワーカーが足りない、が代表的な原因です。.maintenance を消す、プラグインを1つずつ止める、サーバーのエラーログを見る、の順で切り分けます。

Q. 自分だけ503が出るのはなぜですか? A. IP アドレスごとのレート制限、WAF の規則、共有 IP の VPN、短時間に大量のリクエストを送るブラウザの拡張機能が原因のことがあります。別の回線・シークレットウィンドウで試すと切り分けられます。

まとめ

  • 503 Service Unavailable は「過負荷かメンテナンスで今は処理できない」一時的な状態。見ている人は少し待って再読み込みし、連打しない。
  • 作る側が503を返すのは、メンテナンス・同時処理の空きなし・readiness が落ちているとき。相手1人の送りすぎは429、中継役で奥が返さないなら502・504。
  • 503には Retry-After(秒か HTTP 日付)と Cache-Control: no-store を付ける。nginx の add_header は always が要る。メンテナンスは200ではなく503で、robots.txt は200のまま、数日以内に終える。
  • 手元の nginx では limit_req・limit_conn・メンテナンスのフラグで503、振り分け先の全滅では502になった。ALB の503は「ターゲットが無い」の意味(AWS 公式)。
  • クライアントは Retry-After を守り、無ければ指数バックオフ+ジッター、冪等なものだけ、上限つきで再送する。20本のクライアントの実測では、即再送の503が5,016件、Retry-After+ジッターは37件だった。

参考・出典

確認日はいずれも 2026年10月29日です。

  • IETF「RFC 9110 HTTP Semantics」15.6.4 503 Service Unavailable・10.2.3 Retry-After・9.2.2 Idempotent Methods:https://www.rfc-editor.org/rfc/rfc9110#section-15.6.4(本文は HTTP Working Group が公開する rfc9110.xml で確認:https://github.com/httpwg/http-core)
  • IETF「RFC 6585 Additional HTTP Status Codes」4. 429 Too Many Requests:https://www.rfc-editor.org/rfc/rfc6585#section-4
  • IETF「RFC 9457 Problem Details for HTTP APIs」:https://www.rfc-editor.org/rfc/rfc9457
  • MDN「503 Service Unavailable」:https://developer.mozilla.org/ja/docs/Web/HTTP/Reference/Status/503
  • MDN「Retry-After」:https://developer.mozilla.org/ja/docs/Web/HTTP/Reference/Headers/Retry-After
  • MDN「429 Too Many Requests」:https://developer.mozilla.org/ja/docs/Web/HTTP/Reference/Status/429
  • nginx「Module ngx_http_limit_req_module」:https://nginx.org/en/docs/http/ngx_http_limit_req_module.html
  • nginx「Module ngx_http_limit_conn_module」:https://nginx.org/en/docs/http/ngx_http_limit_conn_module.html
  • nginx「Module ngx_http_upstream_module」:https://nginx.org/en/docs/http/ngx_http_upstream_module.html
  • nginx「Module ngx_http_proxy_module」proxy_next_upstream・proxy_intercept_errors:https://nginx.org/en/docs/http/ngx_http_proxy_module.html
  • nginx「Module ngx_http_headers_module」add_header:https://nginx.org/en/docs/http/ngx_http_headers_module.html
  • nginx「Module ngx_http_core_module」error_page:https://nginx.org/en/docs/http/ngx_http_core_module.html#error_page
  • Google 検索セントラル「HTTP ステータスコード、ネットワーク エラー、DNS エラーが Google 検索に与える影響」:https://developers.google.com/search/docs/crawling-indexing/http-network-errors
  • Google 検索セントラル「オンライン ビジネスを一時停止する」:https://developers.google.com/search/docs/crawling-indexing/pause-online-business
  • Google 検索セントラル「Google による robots.txt の指定の解釈」:https://developers.google.com/search/docs/crawling-indexing/robots/robots_txt
  • AWS「Application Load Balancer のトラブルシューティング」HTTP 503:https://docs.aws.amazon.com/elasticloadbalancing/latest/application/load-balancer-troubleshooting.html
  • Kubernetes「Pod Lifecycle」Container probes:https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/
  • Kubernetes「Configure Liveness, Readiness and Startup Probes」:https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/
  • Spring Boot「Endpoints」Kubernetes Probes:https://docs.spring.io/spring-boot/reference/actuator/endpoints.html
  • Spring Boot「SpringApplication」Application Availability:https://docs.spring.io/spring-boot/reference/features/spring-application.html
  • Spring Framework「HttpHeaders (Javadoc)」RETRY_AFTER:https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/http/HttpHeaders.html
  • urllib3「Retry」:https://urllib3.readthedocs.io/en/stable/reference/urllib3.util.html

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「503エラーとは|Service Unavailableの原因・直し方・返し方」

株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?