ByteScope

404

HTTP 404 Not Found の直し方

4xx クライアントエラーIETF 標準RFC 9110

サーバーはリクエストの意味を理解したうえで、そのパスに出せるものを何も持っていません。今どきの構成では、リンク切れよりもルーティングかデプロイの取りこぼしであることのほうがずっと多いコードです。

404 が出ている理由と、やるべきこと

同じ 3 桁でも、リクエストのどちら側にいるかで別々の問題になります。自分に当てはまるブロックを読んでください。

curl で 404 を再現する

失敗した URL に対してそのまま実行してください。エラーページを表示せずにステータスだけを出すので、ブラウザが描画したものではなくサーバーが言ったことが見えます。

curl
curl -sS -o /dev/null -L -w '%{http_code} %{num_redirects} %{url_effective}\n' https://example.com/no-such-page

`-sS` は進捗表示だけを消してエラーメッセージは残し、`-o /dev/null` はエラーページの本文を捨て、`-L` は `Location` ヘッダーを辿り、`-w` は最終的なステータス・辿ったリダイレクト数・そのステータスを返した URL を出します。最後のフィールドがこのコマンドの主眼です。curl 自身のドキュメントが、`%{url_effective}` はリダイレクトを辿らせたときに最も意味を持つと書いていて、リダイレクトの終点で出る 404 は、自分が打った URL で出る 404 とは別のバグ——書き換えが存在しない場所へ送っている、という意味になります。直接叩いたつもりの URL で `%{num_redirects}` が 0 より大きければ、それが手がかりです。エッジとオリジンのどちらが 404 を作ったかは、同じリクエストを 2 回投げれば分かります。1 回はそのまま、もう 1 回は `--resolve example.com:443:203.0.113.10` を付けて。これは元のホスト名と SNI を送りつつ、指定したアドレスへ接続します。答えが食い違うなら、エッジがオリジンには無いものを配っている——このコードの場合、キャッシュされた 404 であることがよくあります。

404 が実際に意味していること

RFC 9110 §15.5.5 は 404 を、オリジンサーバーが対象リソースの現在の表現を見つけられなかったか、あるいはそれが存在することを明かすつもりがない状態、と定義しています。この二つはどちらも効いてきます。前半が意味するのは、404 が語っているのは「いまこの URL」だけであって、そのリソースが以前あったかどうかにも、これから現れるかどうかにも一切触れていないということです。同じ節は、恒久的に無くなったと分かっているなら 410 (Gone) のほうが望ましい、とも書いています。後半は 404 と 403 が重なる理由そのものです。§15.5.4 は、禁止されたリソースの存在自体を隠したいオリジンサーバーが代わりに 404 を返すことを明示的に認めているので、「絶対にあるはずの URL」で出る 404 は、変装したアクセス制御の判断であることがあります。もう一つ、障害の最中に人を驚かせる性質があります。404 は RFC 9110 §15.1 がヒューリスティックにキャッシュ可能と列挙している数少ないコードの一つで、`Cache-Control` を明示していなくてもプロキシや CDN が保存してかまいません。だから壊れたデプロイ中に 404 になった URL は、デプロイを直したあとも、保存された応答が期限切れになるか誰かがパージするまで 404 を返し続けます。そして 404 はメソッドについては何も言っていません。GET しか受けないパスへの POST は 404 ではなく 405 であって、そこで 404 を返すフレームワークは「どちらが違っていたのか」を教えないことを選んでいます。

404 と間違えられやすいコード

紛らわしいコード見分け方
403403 は「意味は分かったうえで拒否する」、404 は「そこには何も無い」です。RFC 9110 §15.5.4 はこの二つをわざと曖昧にしています。禁止されたリソースの存在を認めたくないオリジンサーバーが、代わりに 404 を返すことを明示的に許しているからです。だから「あるはずの URL」で 404 が出たら、ルーティングのバグを探しに行く前に、資格情報を付けてもう一度試す価値があります。オブジェクトストレージのバケットや非公開リポジトリはまさにこれをやります。
410410 (Gone) は、そのリソースが恒久的に終わったとサーバーが分かっているときの答えで、RFC 9110 はその場合 404 より 410 が望ましいと書いています。違いは人向けではなく機械向けの信号です。404 は再試行を招き、410 は索引に「もう聞かなくていい」と伝えます。意図的にコンテンツを畳むなら、410 のほうが正直で、検索結果から消えるのも速いです。
405405 (Method Not Allowed) は「パスはあるが、そのメソッドでは無い」で、RFC 9110 は通るメソッドを列挙した `Allow` ヘッダーを付けることを要求しています。GET 専用ルートへの POST に 404 を返すフレームワークは規格上は許されていますが、答えの役に立つ半分を隠しています。パスを疑う前に、メソッドを確かめてください。
soft 404soft 404 は、本文では「見つかりません」と謝っているのにステータス行は 200、というページです。HTTP としては禁じられていませんが、検索エンジンはエラーページを本文として索引しますし、スクリプトは成功ステータスを見て謝罪文をデータとして解析します。無いと分かっているページが curl で 404 を返さないなら、その食い違い自体がバグです。
nginxnginx が 404 を出す場所は、ファイルが無いときだけではありません。`=404` で終わる `try_files` は意図的に返しますし、`Host` がどの `server_name` にも一致しないリクエストはデフォルトサーバーに落ちて、そこはたいてい root が空のスタブです。末尾スラッシュを失った `alias` は 1 階層足りないパスを組み立てます。エラーログには実際に試したパスが記録されるので、この 3 つを見分けるにはそれが一番速い方法です。
理由句
Not Found
クラス
4xx クライアントエラー
定義
RFC 9110
標準
IETF 標準

このサイトのツール

どれもブラウザ内で完結します。アップロードは発生しません。

関連するステータスコード

404 を調べているとき、ついでに読むことになりがちなコードです。

よくある質問

このステータスコードを出したのがどのサーバーかを知るには?

ページではなく応答ヘッダーを読んでください。curl -sS -o /dev/null -D - https://example.com/path が本文なしでヘッダーだけを出し、serverviacf-ray の 3 つで、答えたのがどの層かが分かります。cf-ray の値があれば応答を扱ったのは Cloudflare で、その値はサポートが聞いてくる ID です。エッジを完全に外して確かめたいなら、--resolve example.com:443:203.0.113.10 を付けて同じリクエストを投げ直します。元のホスト名と SNI を送りつつ、指定したオリジンのアドレスへ接続するので、答えが変われば、エッジとオリジンの言い分が食い違っているということです。

数字のあとの理由句(Not Found など)に意味はありますか?

ありません。分岐条件にしてはいけません。RFC 9112 はクライアントに理由句を無視するよう求めていますし、サーバーは自由に変えられますし、HTTP/2 と HTTP/3 にはそもそも理由句が存在しません。HTTP/1.1 では 404 Not Found として届くものが、HTTP/2 では素の 404 として届きます。このサイトが登録済みの理由句を載せているのは、それが検索される語であり HTTP/1.1 のログに出る文字列だからで、何かのソフトウェアがそれに依存しているからではありません。

このコードで失敗したリクエストは再試行すべきですか?

5xx と 429 は再試行してよく、4xx はしないでください。次に投げても中身は同じだからです。応答に Retry-After が付いていればそれに従ってください。RFC 9110 がまさにこのために定義していて、値は秒数でも日時でも構いません。付いていなければ、ジッター付きの指数バックオフと明確な上限を使い、しかも冪等なメソッドに限ってください。再試行された POST はカードに二重で課金しかねません。すでに失敗しているサーバーへの再試行の嵐は、短い障害を長い障害に変える典型的な経路です。

自分のサーバーが返すステータスコードは変えられますか?

変えられます。守るべき規則は 1 つだけ、そのコードが本当であること。応答を自動で読むもの——検索エンジン、監視、キャッシュ、クライアント側の再試行ロジック——はすべて数字だけで判断し、ページの中身は一切見ません。エラーページを 200 で返せば自分の監視から障害が隠れますし、無いページを 200 で返せばエラーが本文として索引されますし、単に不正なリクエストに 500 を返せば、当番の人をスタックの間違った半分へ送り込むことになります。

404 でまだ詰まっているなら、ステータスコード一覧をすべて見る。あるいは上に戻って、自分の立ち位置に向けて書かれたブロックを読んでください。