Skip to content
ブログに戻る
チュートリアル

HTTPステータスコード完全早見表:1xx〜5xx を実例で解説

HTTP ステータスコード 1xx〜5xx を網羅した実践リファレンス。401 と 403 の違い、301 と 302 の使い分け、SEO への影響まで解説。今すぐチェック。

14 分で読める

HTTPステータスコード早見表:1xx〜5xx を実例で解説

DevTools を開けば、Network タブの半分が真っ赤。同じエンドポイントが本番では 502、ローカルでは 200 を返し、Slack では同僚が「これって 401 と 403 のどっち?」と聞いてくる。HTTP ステータスコードは一見シンプルだ。3 桁の数字、5 つのクラス。だが選択を誤れば情報が漏れ、SEO は壊れ、オンコール当番は地獄を見る。

本稿は、現場で働く開発者向けの HTTP ステータスコード早見表だ。提供するのは次の 3 つ。(1) 現場で遭遇するコードを網羅したクイックリファレンス表、(2) 間違えやすいペア(301 vs 302401 vs 403404 vs 410502 vs 504)の判断マトリクス、(3) curl・fetch・Python requests からステータスコードを確認する方法を示すツール紹介。本稿のコードは現行 HTTP セマンティクス標準である RFC 9110IANA HTTP Status Code Registry に拠る。

クイックリファレンス:HTTP ステータスコード一覧

本番で遭遇するコードをクラス別にまとめた。この表をブックマークしておけば、以降で扱うのは判断に迷うコードだけになる。

コード名称出現場面
100ContinueExpect: 100-continue を付けて大きな POST ボディを送るとき
101Switching ProtocolsWebSocket ハンドシェイク、HTTP/2 アップグレード
103Early Hintsサーバが本レスポンス前に Link ヘッダをプッシュする
200OKGET、PUT、PATCH の標準成功
201Createdリソースを作成する POST(Location を返す)
202Accepted非同期ジョブをキューに投入、処理は未完了
204No ContentDELETE 成功、ボディを返さない PUT
206Partial ContentRange リクエスト、動画シーク、再開可能ダウンロード
301Moved Permanently旧 URL を廃止、検索エンジンがリンクエクイティを引き継ぐ
302Found一時的リダイレクト、元の URL が依然として正規
303See Otherフォーム POST 後の Post/Redirect/Get パターン
304Not ModifiedETag または If-Modified-Since がマッチした条件付き GET
307Temporary Redirect302 と似るが、メソッドとボディが保持される
308Permanent Redirect301 と似るが、メソッドとボディが保持される
400Bad Request不正な JSON、必須フィールド欠落、スキーマ違反
401Unauthorized認証情報なし、もしくはトークン期限切れ
403Forbidden認証済みだが許可されていない
404Not Foundリソースが存在しない(または隠している)
405Method Not AllowedGET 専用エンドポイントへの POST(Allow ヘッダ必須)
408Request Timeoutクライアントのリクエスト送信が遅すぎた
409Conflict楽観ロック失敗、重複キー
410Goneリソースを恒久的に削除、復活しない
415Unsupported Media TypeContent-Type が不正、例:JSON API に XML を送信
422Unprocessable Content構文は有効だが意味が無効(バリデーションエラー)
425Too EarlyTLS 1.3 early-data リプレイ攻撃のリスク
428Precondition Requiredサーバが更新ロスト防止のため If-Match を要求
429Too Many Requestsレート制限(Retry-After ヘッダ必須)
451Unavailable for Legal ReasonsDMCA、GDPR 削除要請、地理的ブロック
500Internal Server Errorサーバ側コードで未処理の例外が発生
501Not Implementedメソッドや機能が未対応(REST では稀)
502Bad Gatewayアップストリームが不正なレスポンスを返した
503Service Unavailableメンテナンスモードや過負荷
504Gateway Timeoutアップストリームが時間内に応答しなかった
507Insufficient StorageWebDAV のディスク容量不足
508Loop DetectedWebDAV における無限リダイレクトや再帰
511Network 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リダイレクト(追加アクションが必要)任意301308 は可、302307 は不可
4xxクライアントエラー(あなたのせい、リクエストを修正)必要(説明文)原則不可
5xxサーバエラー(我々のせい、リトライで解決するかも)必要(説明文)不可

「既定でキャッシュ可能か」の列は重要だ。CDN やブラウザは 301308 を積極的かつ永続的にキャッシュする。本番でリダイレクトコードを誤れば取り返しがつかない。ユーザのブラウザにリダイレクトがキャッシュされてしまうからだ。この点は 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-continue103 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 がリソースを伴わないアクション(例:/login200 OKアクション結果(トークン、ステータス)
POST は成功、レスポンスは空204 No Content(なし)

200201 で迷ったら、こう自問しよう。「サーバは独自 URL を持つリソースを作成したか?」イエスなら 201、ノーなら 200 だ。

3xx:リダイレクト(301 vs 302 vs 307 vs 308)

リダイレクトは最も誤用されるクラスだ。301302307308 の違いは、3 つの直交する問いに帰着する。移動は恒久的か、メソッドは保持されるか、レスポンスはキャッシュ可能か。

301 Moved Permanently

リソースが移動し、戻ってこない。検索エンジンはリンクエクイティを新 URL へ移転する。ブラウザと CDN は 301 を無期限にキャッシュする。/old301/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 Permanently302 Found
メソッドを保持する必要がある308 Permanent Redirect307 Temporary Redirect

特殊ケース:POST → GET(Post/Redirect/Get パターン)を明示したい場合は 303 See Other を使う。

ブラウザ操作主体の HTML ページなら、GET は GET なので 301302 で通常問題ない。API やフォームでは 308307 を優先し、メソッドが書き換えられる事故を避けよう。

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 で 400422 を判断しかねたら、目安はこう。「そもそもパースすらできない」が 400、「パースしたが意味が通らない」が 422

425 Too Early

サーバが、TLS 1.3 early-data リプレイの可能性があるリクエストを処理するリスクを取りたくないときに送る。主に CDN とリバースプロキシで使う。

428 Precondition Required

更新ロスト問題を避けるため、If-MatchIf-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 度』へのオマージュだ。ただし用途は架空ではない。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 メソッドが不正405Allow 必須)
Content-Type が不正415
楽観ロック衝突409
バリデーションエラー(パース可、検証不可)422
レート制限429Retry-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 を混在させる

401403 の使い分けが一貫していなければ、攻撃者は 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 をより早く除外する。多くの場合、単一のクロールサイクル内で。
  • ソフト 404200 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 は任意の 2xxtrue になる。

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 ではメソッドを保持できる 308307 を優先しよう。

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 Continue101 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 を出すのは避けよう。多くのリバースプロキシやロードバランサが正しく扱えず、ジョーク以外の用途はない。