ByteScope

301

HTTP 301 Moved Permanently の直し方

3xx リダイレクトIETF 標準RFC 9110

古い URL は退役し、`Location` に入っている URL がその座を引き継ぎます。しかも 301 は、何も指示しなくてもキャッシュが保存してよい数少ない応答の一つなので、間違って出した 301 はいちばん取り消しにくいリダイレクトになります。

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

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

curl で 301 を再現する

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

curl
curl -sS -D - -o /dev/null -L -w '\n%{num_redirects} hops, final %{http_code} at %{url_effective}\n' http://example.com/old-page

`-D -` は最後のホップだけでなく全ホップの応答ヘッダーを標準出力に落とし、`-o /dev/null` は本文を全部捨て、`-L` は連鎖を辿り、`-w` はホップ数・最終ステータス・それを返した URL を出します。上から順に読んでください。各ステータス行は、そのホップが 301 だったか 302 だったかを教えます。これはキャッシュが保存してよいかどうかの分かれ目です。各 `location:` は宛先の形を教えます。HTTPS へ戻すサイトなのに絶対 `http://` になっていればループですし、内部ホスト名や紛れ込んだ `:8080` は、プロキシがアプリケーションにリクエストの内容を誤って伝えている印です。そして 301 の応答自体に `cache-control` か `expires` があるかを見てください。どちらも無ければ、RFC 9110 §15.1 のヒューリスティックにキャッシュ可能なままで、訪問者が抱え込むのはそのバージョンのリダイレクトです。決定打になる手が 2 つあります。curl はキャッシュを持たないので、ブラウザはリダイレクトするのにこのコマンドがしないなら、そのリダイレクトはあなたの端末にしか存在しません。もう 1 つ、curl は 301・302・303 を辿るとき POST を GET に落とすと自ら明記しているので、同じコマンドを `--post301` 付きで実行してサーバーのログを見比べれば、リクエストボディを食べているのがこのリダイレクトかどうかを証明できます。

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

RFC 9110 §15.4.2 は 301 を、対象リソースに新しい恒久的な URI が割り当てられた状態と定義し、サーバーは推奨する URI を `Location` フィールドに入れて返すこと、そしてユーザーエージェントが手元に持っている古い URI への参照を書き換えてよいことまで明記しています。この定義のうち、実際に人がつまずくのは 3 点です。1 つ目、「恒久的」は設定ファイルに書いたメモではなく、キャッシュに対する約束です。RFC 9110 §15.1 は 301 をヒューリスティックにキャッシュ可能なステータスコードの一つに挙げているので、`Cache-Control` を一切付けていない 301 でも、CDN にもプロキシにもブラウザ自身にも保存され、二度とあなたに確認せず再利用されえます。2 つ目、メソッドは保証されません。§15.4.2 は歴史的な理由からユーザーエージェントが後続のリクエストで POST を GET に変えてもよいと書いていて、その挙動が困る場合に使うコードとして 308 (RFC 7538) を名指ししています。3 つ目、`Location` は URI-reference であり、RFC 9110 §10.2.2 は相対参照を許して対象 URI を基準に解決させます。つまり設定上は正しく見えるリダイレクトでも、それを組み立てた何かがリクエストについて誤った情報を渡されていれば、スキームもホストもポートも違う場所へ着地しえます。そして仕様のどこにも、すでにどこかへ保存された 301 を撤回する手段はありません。今日その 301 を出すかどうかは、結局この一点で決めるべきです。

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

紛らわしいコード見分け方
308308 (RFC 7538) は、メソッドとボディをそのまま再送することを保証する恒久リダイレクトです。RFC 9110 §15.4.2 が 301 について「POST を GET に変えてよい」と認めて手放した保証が、まさにこれです。既定でキャッシュ可能なのは両方なので、308 も同じくらい後を引きます。選ぶ基準は寿命ではなくメソッドのほうです。
302目に見える違いは恒久かどうかですが、金額に効くのはキャッシュ可能性です。301 は RFC 9110 §15.1 の「ヒューリスティックにキャッシュ可能なステータスコード」の一覧に入っていて、302 は入っていません。つまり `Cache-Control` を明示しない 302 は保存されず、次のリクエストで宛先を自由に変えられます。確信が持てるまで 302 で回し、決まってから格上げする——根拠はこれだけです。
HSTS`http://` から `https://` への切り替えが、いつもサーバーのリダイレクトとは限りません。一度そのホストが `Strict-Transport-Security` を送っていれば、ブラウザはリクエストが端末を出る前に URL を書き換えます。Chrome のネットワークパネルでは `307 Internal Redirect` と `Non-Authoritative-Reason: HSTS` として見えます。サーバー側に対応する設定は存在しないので、原因のリダイレクトを設定ファイルから探しても永遠に見つかりません。
Cloudflareリダイレクトは、オリジンに問い合わせる前にエッジで発行されることがあります。Cloudflare の Always Use HTTPS、Bulk Redirects、Redirect Rules はどれもそうです。だからサーバーの設定のどこにも書いていない 301 が実在します。対になる罠は逆向きで、Cloudflare 自身が、Flexible 暗号化と HTTP から HTTPS へリダイレクトするオリジンの組み合わせは無限ループになると書いています。オリジンには、自分が要求している HTTPS が一度も見えないからです。
理由句
Moved Permanently
クラス
3xx リダイレクト
定義
RFC 9110
標準
IETF 標準

このサイトのツール

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

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

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

よくある質問

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

ページではなく応答ヘッダーを読んでください。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 を返せば、当番の人をスタックの間違った半分へ送り込むことになります。

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