冒頭まとめ

Nginx で 429 Too Many Requests に出会ったとき、最初に押さえるべき事実があります。Nginx はレート制限の拒否に、既定で 429 を使いません

公式文書を見ると、頻度を制限する仕組みの応答コードlimit_req_status で指定し、その既定値は 503 です。同時接続数を制限する仕組みも同様で、limit_conn_status の既定値はやはり 503 です。実装を読んでも、どちらも既定値として「サービス利用不可」を表す定数が設定されています。

つまり、Nginx が返した 429 には次のいずれかの理由があります。誰かが limit_req_status 429 または limit_conn_status 429 を明示的に設定した。上流のアプリケーションが返した 429 を、Nginx がそのまま中継している。あるいは、Nginx より前段の仕組みが返している。

逆方向の混乱もよく起きます。「429 を返すよう設定したのに 503 のままだ」という状況です。これは多くの場合、頻度の制限と接続数の制限が別々の設定であることを見落としているために起こります。片方だけ 429 にしても、もう片方が発動していれば 503 が返ります。

もう1つ、実務で効く事実があります。Nginx はレート制限で拒否するとき、待つべき時間を示すヘッダーを付けません。実装を確認しても、頻度と接続数のどちらの仕組みにも該当する記述はありません。429 に設定したとしても、クライアントは「いつ再試行してよいか」を知る手段がないままです。

エラーの概要

まず、既定の設定レート制限に当たった場合の記録です。応答は 503 ですが、記録の文言は制限によるものだと分かる形になっています。

2026/08/03 12:00:00 [error] 1234#1234: *56 limiting requests,
  excess: 0.622 by zone "one", client: 203.0.113.10,
  server: example.com, request: "GET /search/ HTTP/1.1"

limiting requests が頻度の制限、limiting connections が接続数の制限です。どのゾーンで拒否されたかも同じ行に出ます。この文言は応答コードを 429 に変えても変わりません。記録の文言と応答コードは独立している、と押さえてください。

429 を返すよう設定した場合の応答は、次の形になります。

HTTP/1.1 429 Too Many Requests
Server: nginx
Content-Type: text/html

<html>
<head><title>429 Too Many Requests</title></head>
<body>
<center><h1>429 Too Many Requests</h1></center>

Nginx は 429 用の応答文と既定のエラーページを内部に持っているため、表示は他のエラーと同じ体裁になります。ただし前述のとおり、待ち時間を示すヘッダーは付きません。

拒否されたのか、遅延させられただけなのかは、専用の変数で記録できます。$limit_req_status には PASSEDDELAYEDREJECTED、および試験動作時の2種類のいずれかが入ります。この値を記録の書式に加えると、切り分けが一気に楽になります。

まず最初に:出どころを確定する

第一に、エラーの記録に limiting requests または limiting connections があるかを見ます。あれば Nginx 自身が拒否しています。無ければ、中継している可能性が高くなります。

第二に、limit_req_statuslimit_conn_status設定値を確認します。429 が返っているなら、どちらかが明示的に設定されているはずです。

第三に、上流からの応答を確認します。中継であれば、上流側の記録に同じ時刻の 429 が残っています。

第四に、Nginx より前段があるかを確認します。負荷分散装置や配信網が返した 429 は、Nginx の記録には一切残りません。

よくある原因と解決手順

原因1:429 に設定したのに 503 が返る

最も多い混乱です。頻度の制限と接続数の制限は、別々の設定項目を持ちます。

Before(片方だけ設定する):

limit_req_zone $binary_remote_addr zone=one:10m rate=10r/s;

server {
    location / {
        limit_req  zone=one burst=20;
        limit_conn addr 10;
        limit_req_status 429;   # 頻度の制限だけ 429 になる
    }
}

この設定では、接続数の制限に当たった場合は既定の 503 が返ります。設定したはずの 429 が出ないのは、発動しているのが別の仕組みだからです。

After(両方を揃える):

limit_req_status  429;
limit_conn_status 429;

Kubernetes の入口制御で Nginx を使っている場合も同じ構造です。公式文書によれば、頻度の制限は limit-req-status-code、接続数の制限は limit-conn-status-code という別々の項目で指定し、どちらも既定値は 503 です。注記として、複数の指定を1つの規則に書いた場合は接続数・毎分・毎秒の順で適用される、とも書かれています。試験のために接続数の制限を使いながら、頻度側の項目だけを 429 にしていると、いつまでも 503 が返り続けます。

なお、指定できる値の範囲は実装で 400 から 599 に制限されています。範囲外の値を書くと設定の読み込み自体が失敗します。

原因2:待ち時間を伝えられていない

429 に設定しただけで満足すると、この問題に気付きません。前述のとおり、Nginx はレート制限の拒否に待ち時間のヘッダーを付けません。

429 というコードは、クライアント側に「待ってから再試行せよ」と伝える意図で使われます。しかし待つべき秒数が分からなければ、多くのクライアントは即座に再試行します。結果として、拒否される要求がさらに増えます。

After(拒否時に待ち時間を添える):

location / {
    limit_req zone=one burst=20 nodelay;
    limit_req_status 429;
    error_page 429 = @too_many;
}

location @too_many {
    add_header Retry-After 10 always;
    return 429;
}

自前で付ける以外に方法がないため、レート制限を外部に公開する場合は検討する価値があります。プロキシとして上流の 429 を中継する場合は、上流が付けたヘッダーがそのまま届きます。

原因3:制限が意図より厳しい

設定は効いているが、想定より早く拒否される場合です。仕組みの動作を理解していないと、値を変えても改善しません。

公式文書によれば、超過した要求はまず遅延させられ、その数が待ち枠の上限を超えた時点で拒否されます。待ち枠の既定値はゼロです。つまり burst を書かなければ、指定した頻度をわずかでも超えた瞬間に拒否が始まります。

# 1秒あたり1件。待ち枠が無いので、100ミリ秒以内の2件目は即座に拒否される
limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s;
limit_req zone=one;

# 待ち枠を5件分用意する。超過分は遅延で吸収される
limit_req zone=one burst=5;

# 遅延させずに即座に通す(待ち枠は消費する)
limit_req zone=one burst=5 nodelay;

delay を使うと、待ち枠のうち何件目から遅延させるかを指定できます。既定はゼロ、つまり超過分はすべて遅延します。

設定を変更する前に、試験動作の指定を使うと安全です。この指定を有効にすると、実際には制限せずに超過分だけを数えます。本番で値を決める前に、どれだけ拒否されるはずだったかを確認できます。

原因4:上流が返した 429 を中継している

Nginx 自身の記録に制限の文言が無い場合、この可能性が高くなります。実装を読むと、上流からの応答は状態コードが 300 以上のとき置き換えの判定に入りますが、置き換えが起きるのは中継先のエラーを横取りする指定が有効で、かつ該当するエラーページが定義されている場合だけです。どちらかが欠けていれば、上流の 429 はそのままクライアントに届きます。

したがって、この場合に調べるべきは Nginx ではなく上流です。アプリケーション側の実装なのか、その先の外部サービスなのかを確認してください。

# 上流に直接問い合わせて、429 がそこから来ているかを確認する
curl -sI http://127.0.0.1:8080/path | head -1

原因5:共有メモリ領域の枯渇、または前段の存在

制限の状態は共有メモリ領域に保存されます。公式文書には、領域が枯渇した場合は最も古い状態が削除され、それでも新しい状態を作れなければ要求はエラーで終了する、と書かれています。このときのコードlimit_req_status設定値に従います。

領域の見積もりも公式に示されています。1メガバイトあたり、64バイトの状態なら約1万6千件、128バイトの状態なら約8千件です。制限の対象が多い環境では、余裕を持たせてください。

前段の存在も忘れがちです。負荷分散装置、配信網、防御装置が返した 429 は、Nginx を経由していないため記録に残りません。Nginx の記録に何も無いのにクライアントが 429 を受け取っているなら、経路上の別の装置を疑ってください。

補足:似ているが別のもの

既定の設定では、レート制限の拒否は 503 として現れます。503 には他にも、設定に残った明示的な指定や、上流が返した 503 の中継といった原因があります(Nginx の 503 の記事)。429 と 503 は、この文脈では同じ現象の別の表示です。記録の文言で見分けてください。

上流への接続そのものが失敗した場合は 502 です(Nginx の 502 の記事)。応答を待ちきれなかった場合は 504 です(Nginx の 504 の記事)。

同じ 429 でも、他の基盤では応答の中身が違います。GCP では上限の識別子と待ち時間が構造化された形で入ります(GCP の 429 の記事)。Nginx の 429 にはそうした情報が無いため、原因の特定は記録に頼ることになります。

切り分けの順序

  1. エラーの記録に limiting requestslimiting connections があるかを見る。あれば Nginx 自身が拒否している。
  2. 文言が requestsconnections かを確認する。設定すべき項目が違う。
  3. 429 が出ないなら、対応する側の状態コード設定を確認する。片方だけでは足りない。
  4. $limit_req_status を記録に加える。拒否と遅延を区別できる。
  5. 待ち時間のヘッダーが必要なら自分で付ける。Nginx は付けない。
  6. 制限が厳しすぎるなら、待ち枠の既定がゼロであることを思い出す。
  7. 記録に制限の文言が無ければ、上流か前段を疑う。上流には直接問い合わせて確認する。
  8. 値を変更する前に、試験動作の指定で影響を確認する。

確認コマンド集

# 1. 制限による拒否をエラーログから抽出する
grep -E "limiting (requests|connections)" /var/log/nginx/error.log | tail -20

# 2. どのゾーンで拒否が多いかを数える
grep "limiting requests" /var/log/nginx/error.log \
  | grep -oE 'zone "[^"]+"' | sort | uniq -c | sort -rn

# 3. 現在の設定値を確認する(両方あるかを見る)
nginx -T 2>/dev/null | grep -E "limit_(req|conn)(_status|_zone)?\s"

# 4. 設定の妥当性を検査する
nginx -t

# 5. 拒否と遅延を記録に残す(log_format に追加)
#   log_format main '... rl=$limit_req_status';

# 6. 応答コードとヘッダーを実際に確認する
curl -sI http://127.0.0.1/path | head -5

# 7. 上流から来ているのかを直接確認する
curl -sI http://127.0.0.1:8080/path | head -1

# 8. アクセスログで 429 と 503 の比率を見る
awk '{print $9}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head

Editor’s Note

「429 に設定したのに 503 が返る」という報告は、実際に記録として残っています(Still default status code when defining limit-req-status-code)。2018年3月、Kubernetes の入口制御で頻度制限の状態コードを 429 に指定したにもかかわらず、制限が発動すると 503 が返る、という内容です。

報告者の設定を見ると、原因は明快でした。試験に使っていたのは接続数の制限の指定で、状態コードを変更したのは頻度の制限の側だったのです。返答では公式文書の記述が引用され、最後に一行だけ書かれています。頻度の制限と接続数の制限は同じものではない、と。

同じ時期には、そもそも 429 が返らないこと自体を不具合として報告した例もあります(Requests hitting rate limit are sent 503 rather than 429)。制限に当たったのに 503 が返るのは想定と違う、待ち時間を示すヘッダーも欲しい、という内容です。どちらも、レート制限といえば 429 だという前提から出発しています。

しかし Nginx の設計では、レート制限は「今は処理できない」という状態の表明であり、既定の表現は 503 です。429 にしたければ設定する。待ち時間を伝えたければ自分で付ける。Nginx は選択肢を用意しているだけで、既定値を選んではくれません

429 を見たときも、429 が出ないときも、確認する場所は同じです。エラーの記録の文言と、2つの状態コード設定。この2つを見れば、どちらの疑問にも答えが出ます。


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