リダイレクトを設定しようとして、「301 と 302 は聞いたことがあるけれど、303 や 307、308 は何が違うのか」と手が止まった経験はないでしょうか。番号が似ているうえに、どれもブラウザーの画面では「別のページに移動する」という同じ動きに見えます。

ところが、仕様の上では 5 つの番号は役割がはっきり分かれています。見分けるための軸は 3 つだけです。恒久か一時か、メソッドが変わるか、キャッシュされるか。この 3 つを押さえれば、迷う場面はほとんどなくなります。

軸 1: 恒久か一時か

恒久は 301 と 308、一時は 302・303・307 です。

軸 2: メソッド

307 と 308 は変えません。301 と 302 は POST が GET に変わることがあります。

軸 3: キャッシュ

RFC 9110 で、301 と 308 は明示がなくても保存されうる応答です。

迷ったら

恒久なら 301(または 308)、一時なら 302(または 307)、POST の後は 303 です。

5 つもあるせいで起きる、設定ミスの落とし穴

選択肢が多いことは、そのまま間違いやすさにつながります。よくある失敗を挙げます。

  • 一時的なつもりで 301 を返してしまう。 301 は保存されうる応答なので、後から元に戻しても、ブラウザーが古い転送先を覚えたままになることがあります。
  • フォームの送信先に 302 を使い、データが届かなくなる。 302 を受け取ったクライアントが POST を GET に切り替える実装になっていると、本文が消えて届きません。
  • POST の後に 307 を返し、画面を更新するたびに二重送信が起きる。 307 はメソッドを保つため、移動先にも POST が送られます。結果画面を見せたいだけなら 303 が適しています。
  • 古いソフトウェアが 308 を理解できない。 308 は 301 や 302 より新しい番号です。RFC 9110 にも、2014 年に定義されたため、一部のソフトウェアは認識しないかもしれない、という注意があります。

どれも、番号の意味を知っていれば避けられるミスです。以降で、軸ごとに整理していきます。

軸 1: 恒久か一時か

もっとも基本となる区別です。

恒久的な移動: 301 と 308

「このリソースは、新しい URL へ恒久的に移りました。今後の参照は新しい URL に切り替えてください」という意味です。サイトの引っ越し、URL 構造の変更、http から https への完全移行などで使います。検索エンジンも、Google 検索セントラルによれば、301 と 308 を「移動先を正規 URL とする強いシグナル」として扱います。

一時的な移動: 302、303、307

「いまは別の場所にあります。ただし、次回以降も元の URL を使ってください」という意味です。メンテナンス中の振り向け、状況によって行き先を変えるアクセス制御などに向いています。

ここで重要なのは、「一時的」とは、あとで元に戻す予定があるということだという点です。戻す予定がないのに 302 や 307 を使い続けると、検索エンジンやクライアントは「元の URL が本体」と解釈し続けます。

「一時的」を長く続けてよいのか

「一時的」の長さに、仕様上の期限はありません。数日で終わる場合も、数年続く場合もあります。大切なのは期間ではなく、元の URL が引き続き正しい住所であるという意図です。たとえば、混雑時だけ別のサーバーへ案内する仕組みや、利用者の地域に応じて行き先を変える仕組みは、元の URL が常に入口として機能するので、長期間 307 を返し続けても問題ありません。逆に、元の URL を使う予定がなくなったのであれば、番号を恒久的なものに切り替えるのが筋です。

恒久か一時かの区別は、サーバーが「この移動を覚えておいてよいか」をクライアントや検索エンジンに伝える手段でもあります。覚えてよい移動には恒久の番号、覚えてほしくない移動には一時の番号を付ける、と考えると整理しやすくなります。

軸 2: メソッドが変わるか

ここが 5 つの違いの核心です。RFC 9110 の記述を整理すると、次のとおりです。

コード 自動転送でのメソッド RFC 9110 の記述の要点
301 {{status:}} POST を GET に変えてもよい(MAY)
302 {{status:}} POST を GET に変えてもよい(MAY)
303 {{status:}} GET(または HEAD)で取得し直す
307 {{status:}} 変えてはならない(MUST NOT)
308 {{status:}} 変えない

実際のブラウザーの動作は、WHATWG の Fetch Standard に手順として書かれています。リダイレクトを受け取ったとき、状態コードが 301 または 302 で要求メソッドが POST の場合と、状態コードが 303 でメソッドが GET・HEAD 以外の場合は、メソッドを GET に変え、本文を捨てます。307 と 308 は、この書き換えの対象外です。

POST を GET にしたい(結果を見せたい)

  • 303 を使います。
  • 注文確定の後に完了ページを表示する、といった用途です。
  • 画面を更新しても、二重に送信されません。

POST のまま送りたい(処理を渡したい)

  • 307(一時)または 308(恒久)を使います。
  • API の書き込み先を切り替える、といった用途です。
  • 本文とメソッドが、移動先にもそのまま届きます。

本文を持つ要求の落とし穴

Fetch Standard には、もう一つ見落としやすい規定があります。307 や 308 のように本文を再送する場合、本文がストリームのように一度しか読めない形式だと、再送できずにネットワークエラーとして扱われることがあります。ブラウザーの fetch でストリーム本文を送るような場面では、転送を前提にした設計に注意が必要です。

軸 3: キャッシュされるか

RFC 9110 では、300・301・308 が、明示的な保存期間の指定がなくても保存されうる(ヒューリスティックにキャッシュ可能な)ステータスコードとして整理されています。一方、302・303・307 は、Cache-Control や Expires で保存期間が明示された場合にだけ保存の対象になります。

この違いは、運用に直結します。

  • 恒久的な移動(301・308)は、一度ブラウザーに覚えられると、その後の要求はサーバーへ届かず、ブラウザー内で転送が完了することがあります。確認のために何度アクセスしても、サーバーのログに残らない、という現象の正体です。
  • 一時的な移動(302・303・307)は、明示がなければ毎回サーバーへ問い合わせるため、設定を変えた結果がすぐ反映されます。

設定を誤って 301 を公開してしまった場合は、ブラウザー側のキャッシュが残るため、修正しても反映が遅れることがあります。迷う段階では、まず一時的な転送で確認し、確定してから恒久的な転送に切り替えるのが、安全な進め方です。

用途別の選び方

目的から逆引きできるように整理します。

やりたいこと 選ぶコード 理由
サイトを新ドメインへ引っ越す 301 恒久的な移動。検索エンジンへの強いシグナルになる
API の書き込み先を恒久的に変える 308 恒久的で、POST・PUT などのメソッドを保てる
メンテナンス中だけ別ページへ案内する 302 または 307 一時的な移動。後で元に戻せる
一時的な移動で、POST を保ちたい 307 メソッドと本文を保つ
フォーム送信後に完了ページを見せる 303 GET で結果ページを取得させる
http を https へ強制する(恒久) 301 または 308 恒久的な移動

なお、一般的なウェブページ(GET だけで閲覧するもの)の転送であれば、301 と 308、302 と 307 は、利用者から見て同じように動きます。差が出るのは、POST などのメソッドを使う通信です。

3xx の残りの番号も、ざっと確認しておく

リダイレクトの範囲には、ここまで触れなかった番号もあります。

  • 300 Multiple Choices: 複数の選択肢があることを示します。実際に使われる場面はごく限られます。
  • 304 Not Modified: 転送ではありません。保存してあるデータがまだ新しいので、再取得は不要、という意味です。
  • 305 と 306: どちらも現在は使われません。305 は非推奨、306 は欠番です。

実際に設定を行うのは、301・302・303・307・308 の 5 つと考えて差し支えありません。

同じ POST でも、番号しだいで結末が変わる

言葉だけでは違いが実感しにくいので、同じ POST 要求に対して、サーバーが番号を変えて応答した場合の流れを並べます。なお、301 と 302 のメソッド変更は、仕様上は「してもよい」という扱いで、実際の動作はクライアントの実装に依存します。ここでは、Fetch Standard に書かれたブラウザーの手順に沿って示します。

302 を返した場合

clientserver

POST /contact (本文: name=Taro)
302 Found / Location: /thanks
本文を捨てて、GET に切り替えます
GET /thanks
200 OK

307 を返した場合

clientserver

POST /contact (本文: name=Taro)
307 Temporary Redirect / Location: /v2/contact
メソッドも本文も保ったまま、移動先へ再送します
POST /v2/contact (本文: name=Taro)
200 OK

303 を返した場合

clientserver

POST /contact (本文: name=Taro)
303 See Other / Location: /thanks
必ず GET に切り替えて、結果ページを取得します
GET /thanks
200 OK

302 と 303 は、どちらも GET に切り替わる点では結果が似ています。ただし、303 は切り替えることが仕様で定められているのに対し、302 は実装しだいで POST のまま送られる可能性が残ります。「GET に切り替えたい」と明確に伝えたいなら、303 が確実です。逆に「POST のまま届けたい」ことを明確に伝えたいなら、307 や 308 を選びます。サーバーの意図を迷わず伝えられることが、番号を使い分ける最大の利点です。

各コードのレスポンス例

最後に、実際に返すヘッダーの例を並べておきます。どの番号でも、移動先を示す Location ヘッダーを添える点は共通です。

HTTP/1.1 301 Moved Permanently
Location: https://www.example.com/new-page

HTTP/1.1 302 Found
Location: https://www.example.com/temporary

HTTP/1.1 303 See Other
Location: https://www.example.com/result

HTTP/1.1 307 Temporary Redirect
Location: https://www.example.com/temporary

HTTP/1.1 308 Permanent Redirect
Location: https://www.example.com/new-page

状態行の文言(Found、Moved Permanently など)は、HTTP/2 以降では伝送されませんが、番号の意味は変わりません。ログや開発者ツールに番号だけが表示されても、上の表と見比べれば意味を取り違えることはありません。

設定したあとの確認手順

どの番号を選んでも、設定したら実際の応答を確認する習慣が大切です。確認の観点は 3 つです。

  1. 番号が意図どおりか。 curl -I で、返ってきたステータスコードと Location を見ます。
  2. 転送の回数が最小か。 curl -sIL -o /dev/null -w '%{num_redirects} ' URL で回数を確認します。http から https、さらに www の有無を正す、といった複数の転送が重なっている場合は、できるだけ 1 回にまとめると、表示までの時間も短くなります。
  3. メソッドを使う通信でも期待どおりか。 フォームや API の転送では、curl -i -X POST で動作を確かめ、移動先に本文が届いているかをサーバー側のログで見ます。

恒久的な転送(301・308)は、ブラウザーに保存されると取り消しにくくなります。公開前に、一時的な転送で動作を確かめ、問題がなければ恒久的な番号に切り替える流れにしておくと、設定ミスの影響を小さく抑えられます。

迷わないための判断のしかた

最後に、判断を 3 つの質問に落とし込みます。

  1. 元の URL に戻す予定はありますか。 ない(恒久的)なら 301 か 308、ある(一時的)なら 302・303・307 から選びます。
  2. POST などのメソッドを使う通信ですか。 使うなら、メソッドを保つ 307 か 308 を選びます。ただし、結果ページを見せたいだけなら 303 です。
  3. 対象のソフトウェアは新しい番号を理解できますか。 古い環境が混ざる可能性があるなら、301 や 302 のほうが無難な場合があります。

この 3 つを順に確認すれば、ほとんどの場面で答えが出ます。307 の基本は307 Temporary Redirect とはで、302 との違いを深掘りするなら307 と 302 の違いで、恒久的な転送の新しい選択肢は308 Permanent Redirectで、それぞれ詳しく解説しています。