HTTPステータスコード早見表:1xx〜5xx を実例で解説
DevTools を開けば、Network タブの半分が真っ赤。同じエンドポイントが本番では 502、ローカルでは 200 を返し、Slack では同僚が「これって 401 と 403 のどっち?」と聞いてくる。HTTP ステータスコードは一見シンプルだ。3 桁の数字、5 つのクラス。だが選択を誤れば情報が漏れ、SEO は壊れ、オンコール当番は地獄を見る。
本稿は、現場で働く開発者向けの HTTP ステータスコード早見表だ。提供するのは次の 3 つ。(1) 現場で遭遇するコードを網羅したクイックリファレンス表、(2) 間違えやすいペア(301 vs 302、401 vs 403、404 vs 410、502 vs 504)の判断マトリクス、(3) curl・fetch・Python requests からステータスコードを確認する方法を示すツール紹介。本稿のコードは現行 HTTP セマンティクス標準である RFC 9110 と IANA HTTP Status Code Registry に拠る。
クイックリファレンス:HTTP ステータスコード一覧
本番で遭遇するコードをクラス別にまとめた。この表をブックマークしておけば、以降で扱うのは判断に迷うコードだけになる。
| コード | 名称 | 出現場面 |
|---|---|---|
| 100 | Continue | Expect: 100-continue を付けて大きな POST ボディを送るとき |
| 101 | Switching Protocols | WebSocket ハンドシェイク、HTTP/2 アップグレード |
| 103 | Early Hints | サーバが本レスポンス前に Link ヘッダをプッシュする |
| 200 | OK | GET、PUT、PATCH の標準成功 |
| 201 | Created | リソースを作成する POST(Location を返す) |
| 202 | Accepted | 非同期ジョブをキューに投入、処理は未完了 |
| 204 | No Content | DELETE 成功、ボディを返さない PUT |
| 206 | Partial Content | Range リクエスト、動画シーク、再開可能ダウンロード |
| 301 | Moved Permanently | 旧 URL を廃止、検索エンジンがリンクエクイティを引き継ぐ |
| 302 | Found | 一時的リダイレクト、元の URL が依然として正規 |
| 303 | See Other | フォーム POST 後の Post/Redirect/Get パターン |
| 304 | Not Modified | ETag または If-Modified-Since がマッチした条件付き GET |
| 307 | Temporary Redirect | 302 と似るが、メソッドとボディが保持される |
| 308 | Permanent Redirect | 301 と似るが、メソッドとボディが保持される |
| 400 | Bad Request | 不正な JSON、必須フィールド欠落、スキーマ違反 |
| 401 | Unauthorized | 認証情報なし、もしくはトークン期限切れ |
| 403 | Forbidden | 認証済みだが許可されていない |
| 404 | Not Found | リソースが存在しない(または隠している) |
| 405 | Method Not Allowed | GET 専用エンドポイントへの POST(Allow ヘッダ必須) |
| 408 | Request Timeout | クライアントのリクエスト送信が遅すぎた |
| 409 | Conflict | 楽観ロック失敗、重複キー |
| 410 | Gone | リソースを恒久的に削除、復活しない |
| 415 | Unsupported Media Type | Content-Type が不正、例:JSON API に XML を送信 |
| 422 | Unprocessable Content | 構文は有効だが意味が無効(バリデーションエラー) |
| 425 | Too Early | TLS 1.3 early-data リプレイ攻撃のリスク |
| 428 | Precondition Required | サーバが更新ロスト防止のため If-Match を要求 |
| 429 | Too Many Requests | レート制限(Retry-After ヘッダ必須) |
| 451 | Unavailable for Legal Reasons | DMCA、GDPR 削除要請、地理的ブロック |
| 500 | Internal Server Error | サーバ側コードで未処理の例外が発生 |
| 501 | Not Implemented | メソッドや機能が未対応(REST では稀) |
| 502 | Bad Gateway | アップストリームが不正なレスポンスを返した |
| 503 | Service Unavailable | メンテナンスモードや過負荷 |
| 504 | Gateway Timeout | アップストリームが時間内に応答しなかった |
| 507 | Insufficient Storage | WebDAV のディスク容量不足 |
| 508 | Loop Detected | WebDAV における無限リダイレクトや再帰 |
| 511 | Network Authentication Required | ホテルや空港の WiFi のキャプティブポータル |
以降では、各クラスを判断マトリクス、アンチパターン、誤用時の SEO への影響とともに見ていく。
HTTP ステータスコードの仕組み(3 桁の構造)
なぜ 3 桁なのか
HTTP ステータスコードは 10 進 3 桁だ。これは HTTP/0.9 の時代に、パーサが素早く分岐できる程度に小さく、新しいコードを追加できる余裕を持たせた固定幅シグナルが必要だったからだ。3 桁あれば 900 通り(100〜999)が表現でき、十分な余地がある。IANA レジストリで使われているのは現在およそ 60 個に過ぎない。
最初の桁がクラスを表す。2 桁目と 3 桁目はそのクラス内のコードだ。クライアントが 418 を認識できない場合は、汎用の 4xx として処理すべきだ。RFC 9110 §15 はこれを規定している。クライアントは未認識のコードを、そのクラスの x00 として扱わなければならない。
5 つのカテゴリ早見
| クラス | 意味 | ボディ必須? | 既定でキャッシュ可能? |
|---|---|---|---|
1xx | 情報的(暫定、続報あり) | 不要 | 不可 |
2xx | 成功(リクエストが理解され受理された) | 多くの場合必要 | メソッド依存 |
3xx | リダイレクト(追加アクションが必要) | 任意 | 301、308 は可、302、307 は不可 |
4xx | クライアントエラー(あなたのせい、リクエストを修正) | 必要(説明文) | 原則不可 |
5xx | サーバエラー(我々のせい、リトライで解決するかも) | 必要(説明文) | 不可 |
「既定でキャッシュ可能か」の列は重要だ。CDN やブラウザは 301 と 308 を積極的かつ永続的にキャッシュする。本番でリダイレクトコードを誤れば取り返しがつかない。ユーザのブラウザにリダイレクトがキャッシュされてしまうからだ。この点は SEO セクションで再度取り上げる。
URL 構造(リダイレクトコードが操作する対象)を踏み込んで知りたいなら、URLエンコード・デコード実践ガイドでパーセントエンコーディング、クエリ文字列、URL を有効たらしめるバイトレベルのパイプラインを解説している。
1xx(情報的レスポンス)
ほとんどの開発者は何年もの間、1xx を直接目にしない。これは暫定レスポンスで、サーバがクライアントに「まだここにいる、続けて」と伝えるものだ。ブラウザの DevTools は通常これを隠し、ほとんどの HTTP ライブラリは最終レスポンスに統合してしまう。
定義の補強として参照したいなら MDN の HTTP レスポンスステータスリファレンスが読みやすい。
100 Continue
クライアントはヘッダに Expect: 100-continue を載せ、大きなリクエストボディを送る前に待機する。サーバはボディを受け入れる用意があれば 100 Continue を返し、そうでなければ 4xx を返してリクエストを拒否する。これで大きなアップロードの帯域を節約できる。ヘッダ不備で拒否されるなら、200 MB を送り出す意味はない。
curl -v -H "Expect: 100-continue" \
-H "Content-Type: application/octet-stream" \
--data-binary @big-file.bin \
https://api.example.com/upload
verbose 出力に < HTTP/1.1 100 Continue が見えなければ、クライアントがヘッダを取り除いたか、サーバが対応していない公算が高い。
101 Switching Protocols
HTTP 接続を WebSocket や HTTP/2 接続に切り替えるためのハンドシェイクだ。クライアントが Upgrade: websocket を送り、サーバが 101 Switching Protocols を返すと、その後の通信は別プロトコルで進む。チャットアプリ、ライブダッシュボード、コラボツールの Network タブでよく見かける。
103 Early Hints
新しめのコード(RFC 8297、2017 年)で、サーバが本レスポンスの準備が整う前に Link ヘッダで preload ヒントを送れる。ブラウザはサーバがレンダリング中のうちから CSS や JS の取得を始められる。2026 年時点で Cloudflare、Fastly、Vercel はいずれも 103 を本番でサポートする。Chrome で非推奨となった HTTP/2 サーバプッシュに代わる選択肢だ。
HTTP/1.1 103 Early Hints
Link: </styles.css>; rel=preload; as=style
Link: </app.js>; rel=preload; as=script
HTTP/1.1 200 OK
Content-Type: text/html
...
アンチパターン点検。期待しているのにクライアントが 1xx コードをまったく見ないなら、原因はたいていリバースプロキシだ。古い nginx は Expect: 100-continue や 103 Early Hints を取り除いてしまう。サーバが壊れていると決めつける前に、プロキシ設定を確認しよう。
2xx(成功レスポンス)
すべてに 200 OK を返すのは REST API でよくあるコードスメルだ。2xx ファミリーは情報を運ぶことで、クライアントを賢く、キャッシュを効率的にする。
200 OK
標準。GET はリソースを返し、PUT は更新後のリソース(または 204)を返し、PATCH はパッチ適用後のリソースを返す。他のコードを使う理由がなければ 200 でよい。
201 Created
新しいリソースを作成する POST は、201 と新リソースを指す Location ヘッダを返すべきだ。これで RESTful クライアントは、たった今作ったものの正規 URL を発見できる。
HTTP/1.1 201 Created
Location: /api/users/42
Content-Type: application/json
{"id": 42, "name": "Ada Lovelace"}
202 Accepted
サーバはリクエストを受理したが処理は未完了だ。非同期処理に使う。クライアントはポーリングするか、Webhook を購読するか、ステータスエンドポイントを確認する。ボディにジョブ ID を載せて返そう。
204 No Content
成功、ただしボディなし。DELETE(リソースが消えたのに何を返す?)や、クライアントが既に新状態を知っている PUT 操作で使う。フォーム送信のレスポンスが 204 なら、ブラウザは現在のページを変更しない。SPA の fire-and-forget アクションで使える。
206 Partial Content
Range リクエストへのレスポンス。クライアントが Range: bytes=1000-2000 ヘッダで 1000〜2000 バイト目を要求し、サーバはそのスライスだけを返す。動画ストリーミング、再開可能ダウンロード、HTTP ベースのファイル同期はすべて 206 に依存する。
判断:POST に対して 200 vs 201 vs 204
| シナリオ | コード | ボディ |
|---|---|---|
| POST が新リソースを作成 | 201 Created | 新リソース(または ID のみ)+ Location |
| POST が非同期処理を起動、結果はまだない | 202 Accepted | ジョブ ID、ポーリング URL |
POST がリソースを伴わないアクション(例:/login) | 200 OK | アクション結果(トークン、ステータス) |
| POST は成功、レスポンスは空 | 204 No Content | (なし) |
200 か 201 で迷ったら、こう自問しよう。「サーバは独自 URL を持つリソースを作成したか?」イエスなら 201、ノーなら 200 だ。
3xx:リダイレクト(301 vs 302 vs 307 vs 308)
リダイレクトは最も誤用されるクラスだ。301、302、307、308 の違いは、3 つの直交する問いに帰着する。移動は恒久的か、メソッドは保持されるか、レスポンスはキャッシュ可能か。
301 Moved Permanently
リソースが移動し、戻ってこない。検索エンジンはリンクエクイティを新 URL へ移転する。ブラウザと CDN は 301 を無期限にキャッシュする。/old を 301 で /new にリダイレクトしておいて気が変わっても、キャッシュ済みのユーザは(キャッシュをクリアするまで)永遠に /new へ飛ばされ続ける。
歴史的に、ブラウザは 301 でリクエストメソッドを書き換えることがあった(POST → GET)。これを是正するため、HTTP/1.1 で 308 が導入された。
302 Found
一時的なリダイレクト。元の URL が依然として正規だ。検索エンジンはオリジナル URL のインデックスを維持すべきだ。A/B テストのルーティング、メンテナンスページ、「続けるにはログイン」フローなどで使う。
301 同様、ブラウザは歴史的に 302 で POST を GET に書き換えた。POST をリダイレクトしつつ POST のまま保持したいなら、代わりに 307 を使う。
303 See Other
メソッドを必ず GET に書き換える。Post/Redirect/Get パターン。フォームが /submit に POST し、サーバが Location: /thank-you 付きの 303 を返し、ブラウザが GET /thank-you する。サンクスページを更新してもフォームは再送信されない。303 が設計された目的そのものだ。
304 Not Modified
条件付きレスポンス。クライアントが If-None-Match: "abc123"(または If-Modified-Since)を送り、サーバはリソースが変更されたか確認し、変更がなければボディなしで 304 を返す。ブラウザはキャッシュ済みのコピーを使う。CDN とキャッシュ層がサイトを高速化する基本だ。
307 Temporary Redirect
302 と似ているが、メソッドを変更してはならない。POST は POST のまま、ボディも保持される。非 GET リクエストで一時的なリダイレクトをかけたいときに使う。
308 Permanent Redirect
301 と似ているが、メソッドを変更してはならない。POST/PUT を受け付ける API で恒久的リダイレクトをかけるときの、より安全な選択肢だ。
判断マトリクス:どのリダイレクトコードを使うか
| 恒久的(永続キャッシュ) | 一時的(キャッシュ不可) | |
|---|---|---|
| メソッドが GET に変わってもよい | 301 Moved Permanently | 302 Found |
| メソッドを保持する必要がある | 308 Permanent Redirect | 307 Temporary Redirect |
特殊ケース:POST → GET(Post/Redirect/Get パターン)を明示したい場合は 303 See Other を使う。
ブラウザ操作主体の HTML ページなら、GET は GET なので 301 と 302 で通常問題ない。API やフォームでは 308 と 307 を優先し、メソッドが書き換えられる事故を避けよう。
4xx:クライアントエラー(正しい選択)
4xx はクライアントの誤りを意味する。4xx の語彙が豊かなほど、API は使いやすくなる。クライアントはエラー文字列をパースする代わりに、コードで分岐できるからだ。
400 Bad Request
汎用の構文エラー。不正な JSON、構造レベルの必須フィールド欠落、サーバがそもそもパースできないリクエストに使う。リクエストはパースできるがビジネス検証で失敗する場合は 422 を選ぶ。
401 Unauthorized vs 403 Forbidden
HTTP で最も混同されるペアだ。仕組みがわかれば区別は単純だ。
401 Unauthorized:リクエストに有効な認証情報がない。サーバはあなたが誰か知らない。認証情報を再送(またはトークン更新)すれば解決するかもしれない。RFC 9110 §15.5.2 により、レスポンスにはWWW-Authenticateヘッダを含めなければならない。403 Forbidden:サーバはあなたが誰かを知った上で拒否する。同じリクエストを送り直しても無駄だ。別の認証情報か、別の権限が要る。
| 観測される挙動 | 真相 |
|---|---|
401 + WWW-Authenticate: Bearer | トークンなし、期限切れ、または無効 |
ログイン成功後の 403 | ログイン済み、ただしこのユーザはこのリソースにアクセスできない |
ログイン成功後の 401 | バグ(おそらく 403 を意図している) |
アンチパターン:403 を 404 として返す。一部のサイトは未認証ユーザが /admin/dashboard を要求すると 403 を返す。これは /admin/dashboard の存在を漏らしてしまう。GitHub はこれを、メンバーでない人にプライベートリポジトリを 404 として返すことで解決している。あなたから見ればリソースは「存在しない」のだ。これはバグではなく、意図的な情報隠蔽の選択だ。
404 Not Found vs 410 Gone
両者ともに「このリソースはここにない」と告げる。違いは恒久性と SEO だ。
404 Not Found:存在するかもしれないし、しないかもしれない、戻ってくるかもしれない。検索エンジンは確認を続ける。410 Gone:かつて存在し、意図的に削除され、戻らない。検索エンジンはずっと早くインデックスから外す。
商品ページを削除して Google のインデックスからすぐに外したいなら、410 が正解だ。一時的に壊れているだけの URL なら 404 で構わない。
405 Method Not Allowed
URL は存在するが、このメソッドを受け付けない。レスポンスにはサポートされるメソッドを列挙した Allow ヘッダを含めなければならない。
HTTP/1.1 405 Method Not Allowed
Allow: GET, HEAD, OPTIONS
Content-Type: application/json
{"error": "POST is not allowed on this endpoint"}
Allow ヘッダを忘れるのは、自作 REST API における契約違反のワーストワンだ。
408 Request Timeout
クライアントがリクエストを送り始めたが、その後音沙汰がなくなった。サーバは諦めた。アップストリームに関する 504 Gateway Timeout とは違う。408 は「あなた、つまりクライアントが時間をかけすぎた」のだ。
409 Conflict
リクエストが現在の状態と衝突している。多用途は楽観ロック。クライアントが If-Match: "etag-v3" を送るが、サーバの現在の ETag は "etag-v4" のため、更新は 409 で拒否される。
410 Gone
前述の通り、恒久的削除を表す。論理削除済みのレコードを検索インデックスから除去するのに有用だ。
415 Unsupported Media Type
クライアントが、サーバが理解できないボディを送ってきた。JSON 専用の API に XML を POST すると 415 になる。レスポンスでは受理可能なタイプを示唆すべきだ。
422 Unprocessable Content
リクエストはパースできるが、意味検証に失敗した。RFC 9110 は 2022 年にようやくこのコードを WebDAV からコア仕様に格上げした。バリデーションエラーには 422 を使う。
{
"error": "validation_failed",
"details": [
{"field": "email", "message": "must be a valid email"},
{"field": "age", "message": "must be at least 13"}
]
}
API で 400 と 422 を判断しかねたら、目安はこう。「そもそもパースすらできない」が 400、「パースしたが意味が通らない」が 422。
425 Too Early
サーバが、TLS 1.3 early-data リプレイの可能性があるリクエストを処理するリスクを取りたくないときに送る。主に CDN とリバースプロキシで使う。
428 Precondition Required
更新ロスト問題を避けるため、If-Match か If-Unmodified-Since の送信をサーバが要求する。共同編集 API などで使う。
429 Too Many Requests
レート制限に達した。レスポンスには Retry-After(秒数または HTTP 日付)を含めなければならない。行儀のよいクライアントはこれでバックオフできる。
HTTP/1.1 429 Too Many Requests
Retry-After: 30
Content-Type: application/json
{"error": "rate_limited", "limit": 100, "window": "1m"}
451 Unavailable for Legal Reasons
番号は『華氏 451 度』へのオマージュだ。ただし用途は架空ではない。DMCA テイクダウン、GDPR の忘れられる権利による削除、国単位の地理的ブロックはいずれも 451 の正当な理由になる。RFC 7725 により、レスポンスにはブロックを要求している法的当局を指す Link ヘッダを含めるべきだ。
418 I’m a Teapot(イースターエッグ)
これは本物だ。RFC 2324(1998 年エイプリルフール)由来で、冗談で実装した製品が多すぎたため IETF も帳簿に残し続けている。本物の API で 418 を出してはいけない。リバースプロキシやロードバランサの多くが正しく扱えない。
判断マトリクス:どの 4xx を使うか
| 状況 | コード |
|---|---|
| ボディが不正、パース不能 | 400 |
| 認証情報なし、トークン期限切れ | 401 |
| 認証済みだが許可されていない | 403 |
| URL が存在しない(または隠している) | 404 |
| URL は存在したが意図的に削除 | 410 |
| HTTP メソッドが不正 | 405(Allow 必須) |
Content-Type が不正 | 415 |
| 楽観ロック衝突 | 409 |
| バリデーションエラー(パース可、検証不可) | 422 |
| レート制限 | 429(Retry-After 必須) |
| 法的理由でブロック | 451 |
5xx:サーバエラー(壊れているのはこちら側)
5xx は「我々のせい」だ。オンコールエンジニアが気にするのは、深夜 3 時に自分を起こしたのがどの 5xx だったかだ。コードによって、最初に調査すべき層がわかる。
500 Internal Server Error
万能の受け皿。ほぼ必ず、未処理の例外がフレームワークの既定ハンドラまで浮上したことを意味する。原因については何も語らない。だからこそ、ここではステータスコード以上に構造化ログが効く。
501 Not Implemented
サーバがそのメソッドをまったくサポートしていない。405(この URL ではこのメソッドを許可していない)とは違い、501 は「このサーバは PROPFIND が何かさえ知らない」と告げる。REST API では稀だ。
502 Bad Gateway
リバースプロキシまたはロードバランサが、アップストリームから無効なレスポンスを受け取った。アップストリームは応答したが、内容がゴミだ(プロトコル誤り、ヘッダ不正、応答途中で接続切断など)。CDN から 502 が来ているなら、オリジンがクラッシュしているか、切り詰められたボディを返している公算が高い。
503 Service Unavailable
サーバが意図的に今リクエストを受けていない。メンテナンス時間や、過負荷時の穏当な拒否レスポンスで使う。Retry-After を含めるべきだ。
504 Gateway Timeout
リバースプロキシがアップストリームを待ったが、時間内に応答が来なかった。アップストリームが遅いか詰まっている。アップストリームがゴミを返した 502 とは違う。
502 vs 504:オンコール診断
| 観測 | まず確認すべきこと |
|---|---|
502 Bad Gateway | アップストリームが無効データを返している(オリジンログでクラッシュ、不正レスポンス、プロトコル不整合を確認) |
504 Gateway Timeout | アップストリームがハングしている(オリジンの CPU、DB クエリ、下流 API 呼び出し、プロキシの proxy_read_timeout を確認) |
よくある混同:60 秒かかる DB クエリは、プロキシが 30 秒でタイムアウトすれば 504 として現れる。だがアプリサーバが 90 秒でタイムアウトして例外を投げれば 500 になる。根本原因は同じでも、コードもログ行も別物だ。ダッシュボードでは両方を可視化させよう。
507 Insufficient Storage
WebDAV 専用。サーバのディスクが満杯。WebDAV でない API でこれを見たら、誰かが意味を拡大解釈している。
508 Loop Detected
WebDAV PROPFIND 操作の無限再帰。ほとんど見ない。
511 Network Authentication Required
キャプティブポータル用コード。ホテルや空港の WiFi が 511 を送って、ブラウザに「先にポータルへログインせよ」と告げる。レスポンスにはポータルページへの Location が含まれる。
トラブルシューティングマトリクス:どの層を最初に確認すべきか
| コード | アプリ | プロキシ | DB | ネットワーク |
|---|---|---|---|---|
500 | はい | × | あるかも(DB 例外未捕捉) | × |
502 | × | はい(アップストリーム不正) | × | あるかも(TCP リセット) |
503 | はい(メンテフラグ) | はい(レート制限拒否) | × | × |
504 | はい(ハンドラが遅い) | はい(タイムアウト設定) | はい(クエリが遅い) | はい(DNS、パケットロス) |
HTTP ステータスコードのよくあるアンチパターン
コードレビューで遭遇する悪いコードのほとんどは、次の 5 つに収まる。
1. エラーを 200 OK に包む
HTTP/1.1 200 OK
{"success": false, "error": "user_not_found"}
監視ツール、CDN、キャッシュはどれもリクエストが成功したと判断する。リトライロジックは機能しない。ステータスコードを意識するロードバランサは、不正なトラフィックを「健全な」バックエンドへ振り分けてしまう。このパターンは JSON-RPC に由来し、GraphQL に引き継がれた。GraphQL は部分成功にフィールド単位のエラー報告が必要なため理にかなっている。だが REST に言い訳はない。クライアントエラーには 4xx、サーバエラーには 5xx を返し、構造化された詳細はボディに置こう。
2. 401 と 403 を混在させる
401 と 403 の使い分けが一貫していなければ、攻撃者は API を探って、どのリソースが存在するかを推定できる。方針を決めよう。「これを見られない」に対して 404 を返す(GitHub のプライベートリポ流)か、403 を一貫して返すかだ。一貫性のなさは情報を漏らす。
3. 403 を 404 で隠す
正しい場合もあるが、バグであることも多い。GitHub がプライベートリポに 404 を返すのは意図的だ。リポの存在自体が機微情報だからだ。だが「このユーザアカウントは凍結中」に 404 を返すと、正規ユーザはユーザ名のタイプミスか凍結かを区別できなくなる。方針は明文化し、一貫して適用しよう。
4. 既定の例外受け皿として 500 を使う
フレームワークが安易に成立させてくれるところが問題だ。未捕捉の例外がすべて 500 になり、アラートは「DB がダウン」と「ユーザが不正な UUID を渡した」を区別できない。バリデーションエラーを捕捉して 400 または 422 を返そう。ORM の NotFound を捕捉して 404 を返そう。500 は本当に予期せぬ失敗のために取っておき、発生時はリクエスト ID をログに残して相関できるようにしよう。
5. 長いリダイレクトチェーン
ホップごとにラウンドトリップが発生する。/old → /intermediate → /canonical だと、最悪 DNS 検索が 2 回、TCP ハンドシェイクが 2 回余分に発生する。Google は 3 ホップを超えるチェーンに対してクロール優先度を下げ、ブラウザはループ防止のためリダイレクトチェーンを 20 程度で打ち切る。チェーンは発生源(CDN 設定、もしくはアプリのリダイレクトマップ)で潰そう。
HTTP ステータスコードと SEO
検索エンジンはステータスコードを、URL を保持・除外・移転すべきかの権威あるシグナルとして扱う。誤れば順位が動く。
301 vs 302(リンクエクイティ)
301 Moved Permanently は PageRank を移転する。Google は新 URL を、旧 URL を指していたあらゆるシグナルの正規の宛先として扱う。302 Found はリンクエクイティを移転しない(または、Google のヒューリスティクス次第でゆっくり移転する)。URL を恒久的にリネームしたなら 301 を使う。ゲストを /login にリダイレクトするなら 302 だ。
404 vs 410 vs ソフト 404
Google は「見つからない」状態を 3 種類区別する。
404 Not Found:Google は定期的に再確認し、URL をしばらくインデックスに保持する。410 Gone:Google は URL をより早く除外する。多くの場合、単一のクロールサイクル内で。- ソフト 404:
200 OKを返しつつ「見つかりません」メッセージを表示するページに対する Google の用語だ。Google はコンテンツパターンからこれを検出し、結局404として扱う。だがあなたはクロール 1 回分を浪費し、本物のコンテンツを希釈してしまった可能性がある。
古いインデックスを掃除中なら、恒久削除した URL には本物の 410 を返そう。
5xx とクロールバジェット
Google のクローラはサイトが永続的に 5xx を返すと、レートを下げる。Search Console のクロール統計レポートにこれが現れる。5xx エラーの持続的なスパイクは数日にわたってクロールバジェットを下げ、新規ページのインデックス化が遅れる。5xx の発生率は単なる信頼性指標ではなく、SEO 指標として扱おう。
壊れているのに 200 OK
エラーページに 200 OK を返す(ソフト 404 アンチパターン)のは、SEO 上最悪のケースだ。Google はそのエラーメッセージをインデックスし、何にもランクされず、ページが壊れていると徐々に気づく。SPA が親切なエラー UI を描画する場合でも、サーバからは常に正しいステータスコードを返そう。
HTTP ステータスコードの確認方法(ツール)
見えないものは直せない。現場の開発者なら、以下のうち少なくとも 3 つは習熟しておくべきだ。
ブラウザ DevTools の Network パネル
Chrome、Firefox、Safari はいずれも Network タブにステータス列を表示する。表示されていなければ、列ヘッダを右クリックしてステータステキストを追加しよう。便利な小技:
- ログを保持:ナビゲーションを跨いでエントリを残し、リダイレクトチェーン全体を見られる。
- ステータスでフィルタ:
status-code:5xxと入力(Chrome)してサーバエラーだけを表示。 - XHR を再実行:任意のリクエストを右クリック → Replay XHR でページをリロードせずに再送できる。
リダイレクトでは、リクエストを展開してホップごとのコードを確認できる。
curl(万能の答え)
curl は何でも見せてくれる。デバッグの 9 割を解決する 3 パターン:
# ステータスコードのみ
curl -o /dev/null -s -w "%{http_code}\n" https://api.example.com/users/1
# ヘッダのみ(HEAD リクエスト、リダイレクトに追従)
curl -I -L https://example.com
# リクエストとレスポンスのヘッダを含む詳細出力
curl -v https://api.example.com/users/1
クエリ文字列に特殊文字を含むテスト URL を組み立てるときは、--data-urlencode で curl にエンコードを任せるか、URL を URLエンコード・デコード ツールに貼り付けて、ワイヤを流れるバイト列を確認しよう。
# curl がクエリ値をエンコードしてくれる
curl -G "https://api.example.com/search" \
--data-urlencode "q=hello world & friends"
# 送信される: GET /search?q=hello%20world%20%26%20friends
JavaScript fetch
Response.status プロパティに整数コードが入る。Response.ok は任意の 2xx で true になる。
const res = await fetch('https://api.example.com/users/1');
console.log(res.status); // 200
console.log(res.statusText); // "OK"
console.log(res.ok); // true
if (!res.ok) {
if (res.status === 401) {
// refresh token and retry
} else if (res.status === 429) {
const retryAfter = Number(res.headers.get('Retry-After')) || 1;
await new Promise(r => setTimeout(r, retryAfter * 1000));
} else if (res.status >= 500) {
throw new Error(`Server error: ${res.status}`);
}
}
axios では同じロジックがインターセプタに収まる。
import axios from 'axios';
axios.interceptors.response.use(
response => response,
error => {
const status = error.response?.status;
if (status === 401) {
// redirect to login
}
return Promise.reject(error);
}
);
Python requests
import requests
r = requests.get('https://api.example.com/users/1')
print(r.status_code) # 200
print(r.reason) # 'OK'
# Raises requests.exceptions.HTTPError for 4xx/5xx
r.raise_for_status()
# Manual handling
if r.status_code == 429:
retry_after = int(r.headers.get('Retry-After', '1'))
time.sleep(retry_after)
elif 500 <= r.status_code < 600:
raise RuntimeError(f'Server error: {r.status_code}')
raise_for_status() は「4xx/5xx で大声でこける」ための Python のイディオムだ。status_code で分岐する代わりに例外を投げたいスクリプトで使う。
Postman と Bruno
どちらもテストスクリプト内でステータスコードをアサートできる。
// Postman/Bruno test script
pm.test("Status is 201", () => {
pm.response.to.have.status(201);
});
pm.test("Has Location header", () => {
pm.expect(pm.response.headers.get('Location')).to.match(/^\/users\/\d+$/);
});
CI でステージングに対してこれを走らせ、本番に届く前に契約違反を捕まえよう。
FAQ
401 と 403 はどう違うのか
401 Unauthorized はサーバがあなたを誰だか知らない状態を意味する(認証情報がない、期限切れ、無効)。403 Forbidden はサーバがあなたを認識した上で拒否する状態だ。別の認証情報を送れば解決しそうなら 401、そうでなければ 403 を使う。
301 と 302 はどう使い分けるのか
恒久的な移動(旧 URL は二度と戻らず、検索エンジンに新 URL へリンクエクイティを移転してほしい場合)には 301 を使う。元の URL が依然として正規である一時的なリダイレクト(ログインフロー、A/B テスト、メンテナンスページ)には 302 を使う。API ではメソッドを保持できる 308 と 307 を優先しよう。
502 Bad Gateway エラーは何を意味するのか
502 はリバースプロキシまたはロードバランサが、アップストリームサーバから無効なレスポンスを受け取ったことを意味する。アップストリームは応答したが、内容がゴミだ(プロトコル誤り、ヘッダ不正、接続切断など)。アップストリームがまったく応答しなかった 504 Gateway Timeout とは違う。最初に確認すべきは、オリジンサーバのログでクラッシュや切り詰められたレスポンスがないかだ。
「ソフト 404」とは何か
「ソフト 404」は、200 OK を返しながら実際は「見つかりません」メッセージを表示するページのことだ。Google はこれをヒューリスティックに検出し、結局 404 として扱う。クロールバジェットを浪費し、本物のコンテンツを希釈する。SPA が親切なエラー UI を描画する場合でも、サーバからは常に本物の 404 または 410 ステータスコードを返そう。
400 ではなく 422 を使うべきなのはどんなとき
サーバがリクエストをパースすらできないとき(不正な JSON、構造的フィールド欠落、構文エラー)には 400 Bad Request を使う。リクエストはパースできるがビジネス検証に失敗するとき(メール形式不正、値の範囲外、フィールド間の整合性エラー)には 422 Unprocessable Content を使う。要するに、構文には 400、意味には 422 だ。
429 Too Many Requests への対処は
Retry-After ヘッダ(秒数または HTTP 日付)を読み、少なくともその時間だけバックオフしてからリトライする。Retry-After が欠落していれば、1 秒程度から始めて指数バックオフ + ジッタを使う。即座にリトライしてはいけない。BAN される近道だ。
1xx 情報的コードは 2026 年でもまだ使われているのか
使われているが、ほとんどはアプリケーションコードからは見えない。100 Continue と 101 Switching Protocols は HTTP/1.1 の基本機能だ。103 Early Hints は Cloudflare、Fastly、Vercel が本レスポンスの前に preload ヒントを送る用途で利用拡大中で、Largest Contentful Paint を改善する。ほとんどの HTTP ライブラリは 1xx を最終レスポンスに統合してしまうため、通常は DevTools か curl -v でしか目にしない。
418「I’m a teapot」は本当に存在するステータスコードなのか
存在する。RFC 2324 は 1998 年のエイプリルフールジョークだが、実装した製品が多すぎたため、IETF は RFC 7168 で帳簿に残し続けた。本番で 418 を出すのは避けよう。多くのリバースプロキシやロードバランサが正しく扱えず、ジョーク以外の用途はない。