結論

リダイレクトとは、ある URL を開いた人やプログラムを、自動で別の URL へ移す仕組みです。Web ではサーバーが「3xx のステータスコード」と「Location(移動先)」を返し、ブラウザがその URL を開き直します。URL を戻さない移転は301か308、一時的な転送は302か307、フォーム送信のあとに結果ページへ移すなら303です。

この記事でわかること

  • 301・302・303・307・308の違いと、POST が GET に変わるかの実測
  • 301がブラウザにキャッシュされる様子と、ループ(ERR_TOO_MANY_REDIRECTS)の原文
  • nginx・.htaccess・Next.js・Express・PHP の設定例と、SEO での扱い(Google 公式)

2026年10月29日更新|nginx 1.24・Apache 2.4.58・Next.js 16.4・Chromium 153で実行確認

リダイレクトは、引っ越しのときの「転居届」に近い仕組みです。前の住所(URL)に届いた郵便を、新しい住所へ回してもらいます。Web では、この転送をサーバーがその場で返事として行います。

flowchart TD
    A[ブラウザが<br>前の URL を開く] --> B[サーバーが 3xx と<br>Location を返す]
    B --> C[ブラウザが<br>Location の URL を開く]
    C --> D[200 OK<br>新しいページを表示]
    B -.->|301・308| E[ブラウザが<br>転送を覚える]

よく調べられることの答えを先にまとめます。

知りたいこと 答え 詳しい節
リダイレクトとは 開いた URL から別の URL へ自動で移す仕組み。3xx と Location で行う リダイレクトとは(意味と仕組み)
301と302の違い 301は恒久(URL を戻さない)、302は一時。検索結果に出る URL も変わる 301・302・303・307・308の違い
307・308とは POST を POST のまま、本文も付けて転送する。307は一時、308は恒久 POSTがGETに変わるか
301がキャッシュされる 手元の Chromium では、2回目はサーバーに問い合わせずに転送した 301はブラウザにキャッシュされる
meta refresh・JavaScript との違い HTML や JS で移す方法。Google はサーバー側の転送を勧めている meta refresh・JavaScriptとの違い
リダイレクトの SEO 301・308は転送先を正規 URL の候補として扱う。保つ期間は一般に1年以上 リダイレクトとSEO
ERR_TOO_MANY_REDIRECTS 転送が繰り返されている。手元では20回目の転送でエラーになった リダイレクトループの原因と直し方
リダイレクトの確かめ方 curl -sIL URL で、たどった全部のステータスと Location が見える リダイレクトの確かめ方

確認環境:Ubuntu 24.04.5 LTS の上で、nginx 1.24.0(Ubuntu)と Apache 2.4.58(Ubuntu)をパッケージから作業場所に展開し、自分の設定ファイルを指定して 127.0.0.1 のポートだけで起動しました(システムの設定ファイルは使っていません)。301・302・303・307・308を自在に返す自作サーバーは Python 3.13.18 の標準ライブラリ(http.server)で作り、受け取ったメソッド・本文・Referer をすべて記録しています。フレームワークは Express 5.3.0(Node.js 22.23.4)、Next.js 16.4.3(React 19.3.1、テレメトリーを切って build と start)、PHP 8.3.6 の組み込みサーバーで実行しました。応答は curl 8.5.0 の curl -v・-L、ブラウザの挙動(フォームの POST・fetch・キャッシュ・ループ・meta refresh・履歴)は Chromium 153.0.8010.12(playwright-core 1.63.0・日本語の画面)で実測しています。各ステータスコードの意味は MDN(RFC 9110 を出典とする解説)、SEO での扱いは Google 検索セントラルと Search Console ヘルプ、開発者ツールの操作は Chrome for Developers の公式ドキュメントで確かめた範囲だけを書いています。図は mermaid 11.16.0 で描画を確かめました(確認日 2026年10月29日)。

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「リダイレクトとは|301・302・307・308の違いとPOST・キャッシュを実測」

リダイレクトとは(意味と仕組み)

この節では、リダイレクトの意味と、サーバーとブラウザの間で実際にどんなやり取りが起きているかが分かります。

リダイレクト(redirect)は「向きを変える」という意味の英語で、Web では「開いた URL から別の URL へ自動で移すこと」を指します。

中身は、サーバーが返す1回の返事です。手元の nginx に、/old-301 を /new.html へ移す設定をして、curl -v で見た結果です(> が送った内容、< が返ってきた内容)。

$ curl -v http://127.0.0.1:45451/old-301
> GET /old-301 HTTP/1.1
> Host: 127.0.0.1:45451
> User-Agent: curl/8.5.0
>
< HTTP/1.1 301 Moved Permanently
< Server: nginx/1.24.0 (Ubuntu)
< Content-Type: text/html
< Content-Length: 178
< Location: http://127.0.0.1:45451/new.html
<
<html>
<head><title>301 Moved Permanently</title></head>
  • 1行目の 301:ステータスコード(サーバーが返す3桁の結果番号)。300番台(3xx)が「別の場所を見てください」という意味です。
  • Location ヘッダー:移動先の URL。ブラウザはこの URL を自動で開き直します。
  • 本文:nginx は小さな HTML を付けますが、ブラウザは通常これを表示せずに Location へ進みます。

curl は、何も付けなければ転送をたどりません。-L を付けると、ブラウザと同じように Location をたどります。

サーバー側のリダイレクトとクライアント側のリダイレクト

サーバー側(HTTP リダイレクト)

3xx と Location をサーバーが返す

ページの中身を読む前に移る

nginx・Apache・アプリで設定する。いちばん確実

クライアント側(HTML・JavaScript)

200で返したページの中の meta refresh や location で移る

ページを読み込んでから移る

サーバーの設定を変えられないときの手段

リダイレクトが使われる場面

1

URL の変更・サイトの移転

ページの住所やドメインを変えたとき、前の URL のブックマークやリンクから来た人を新しい URL へ移す。

301か308

2

http から https へ・www の統一

同じページが複数の URL で見えないように、1つの形にそろえる。

301か308

3

末尾の「/」をそろえる

フォルダーの URL を「/」なしで開くと、nginx・Apache は自動で「/」付きへ301で移した(手元で確認)。

サーバーが自動で行う

4

ログインが必要なページ

ログインしていない人をログイン画面へ移し、ログイン後に元のページへ戻す。

302・303・307

5

フォームを送ったあと

登録の POST のあと、完了ページへ移す。再読み込みで二重に送られるのを防ぐ。

303

301・302・303・307・308の違い(一覧表)

この節では、リダイレクトの5つのステータスコードが何を意味し、どう使い分けるのかが一覧で分かります。

違いは2つの軸で決まります。「恒久(もう戻さない)か一時か」と「POST を POST のまま転送するか」です。 意味は MDN(RFC 9110 を出典とする解説)、POST の扱いは手元の Chromium で実測した結果です。

コード 名前 恒久か一時か フォームの POST(Chromium 実測) 主な用途
301 Moved Permanently 恒久 GET に変わり、本文は消えた URL の変更・サイトの移転・https 化
302 Found 一時 GET に変わり、本文は消えた 一時的な転送・ログイン画面への誘導
303 See Other 一時 GET に変わり、本文は消えた(仕様どおり) POST・PUT のあとに結果ページを見せる
307 Temporary Redirect 一時 POST のまま、本文 a=1 も届いた API など GET 以外の一時的な転送
308 Permanent Redirect 恒久 POST のまま、本文 a=1 も届いた API など GET 以外を含む恒久的な移転

恒久(301・308)

「この URL はもう使わない」と伝える

検索エンジンは転送先の URL を表示するようになる

ブラウザが転送を覚える(次の節で実測)

一時(302・303・307)

「今だけ別の場所を見て」と伝える

検索結果には転送元の URL が残る

ブラウザは毎回サーバーに問い合わせた

MDN は、302について「GET 以外のメソッドは GET に変更されることがある」とし、GET 以外の操作を扱うなら302より307を勧めています。301と308の関係も同じです。迷ったら、ページの移転は301、API の移転は308、フォーム送信後の画面遷移は303、と覚えると使い分けがぶれません。

手元の nginx 1.24 は302のステータス行を「302 Moved Temporarily」(HTTP/1.0 時代の名前)、Apache・Express・PHP は「302 Found」と書きました。クライアントは名前ではなく数字で判断するため、動きは変わりません。

ほかのステータスコードの意味と対処は、関連記事「HTTPステータスコード 一覧|全コードの原因と対処・curl実例つき」で扱っています。

POSTがGETに変わるか:ブラウザとcurlで実測

この節では、フォームや API の POST がリダイレクトされたとき、送った中身がどうなるのかが、実際の記録で分かります。

301・302・303では POST が GET に変わり、送った本文は転送先に届きませんでした。307・308では POST のまま、本文も届きました。 フォームの送信先を301で移すと、入力した内容が消えます。

Chromium でフォームを送った結果

自作サーバーに、a=1 を POST するフォームを置き、送信先を /s/301 〜 /s/308 にしました。どれも /echo へ転送し、/echo は受け取ったメソッドと本文をそのまま返します。

301 {"method": "GET", "body": "", "referer": "http://127.0.0.1:45452/form/301"}
302 {"method": "GET", "body": "", "referer": "http://127.0.0.1:45452/form/302"}
303 {"method": "GET", "body": "", "referer": "http://127.0.0.1:45452/form/303"}
307 {"method": "POST", "body": "a=1", "referer": "http://127.0.0.1:45452/form/307"}
308 {"method": "POST", "body": "a=1", "referer": "http://127.0.0.1:45452/form/308"}

JavaScript の fetch() で POST したときも、結果は同じでした。あわせて、転送をたどらない指定(redirect オプション)の結果も記録しました。

fetch の指定 301・302・303 307・308
既定(redirect: 'follow') 200・redirected: true・転送先には GET で本文なし 200・redirected: true・POST で本文 a=1
redirect: 'manual' type: opaqueredirect・status: 0 同じ
redirect: 'error' TypeError: Failed to fetch 同じ

redirect: 'manual' では、ブラウザの JavaScript から Location の値もステータスも読めませんでした(status は 0)。ブラウザの fetch で「転送されたかどうか」を知りたいときは、res.redirected と res.url(最後にたどり着いた URL)を見ます。

curl で POST を転送したときの動き

$ curl -vL -d a=1 http://127.0.0.1:45452/s/302
> POST /s/302 HTTP/1.1
< HTTP/1.1 302 Found
< Location: /echo
* Ignoring the response-body
* Issue another request to this URL: 'http://127.0.0.1:45452/echo'
* Switch from POST to GET
> GET /echo HTTP/1.1
< HTTP/1.1 200 OK
curl の指定 301 303 307・308
-L -d a=1 GET・本文なし GET・本文なし POST・本文 a=1
-L -X POST -d a=1 POST・本文なし POST・本文なし POST・本文 a=1
-L --post301 -d a=1 POST・本文 a=1 (対象外) POST・本文 a=1

curl で POST を転送先まで送りたいとき

やってはいけない-X POST を付けて -L する。手元ではメソッドだけ POST のまま、本文が空で届いた
→
代わりに--post301・--post302・--post303 を使うか、サーバー側を307・308にする

「POST のつもりで本文が空」は、API の不具合調査で見落としやすい

API の URL を移すとき

やってはいけないPOST を受ける URL を301で移す。前の版のクライアントの POST が GET になり、登録が消える
→
代わりに308(恒久)か307(一時)で移す

本文を保つかどうかは、コードの数字だけで決まる

303は「POST のあとに結果ページへ」のためのコード

フォームを POST した直後のページを再読み込みすると、ブラウザは同じ POST をもう一度送ろうとします。POST の処理が終わったら303で結果ページへ移し、結果ページは GET で表示する、という形(Post/Redirect/Get、略して PRG)にすると、再読み込みしても二重登録になりません。MDN も303の用途として、PUT や POST のあとにリダイレクトし、再読み込みで操作が繰り返されないようにする例を挙げています。

301はブラウザにキャッシュされる(実測)

この節では、301・308がブラウザに覚えられる様子と、それが設定の変更時に問題になる理由・避け方が分かります。

301・308は、特に指定が無くてもブラウザにキャッシュされました。 1回目に開いたあと、別のページを挟んで同じ URL をもう一度開くと、Chromium はサーバーに問い合わせずに転送先へ移りました。302・307は毎回サーバーに問い合わせています。

1回301・308を2回開いたとき2回目はサーバーに届かなかった
2回302・307を2回開いたとき毎回サーバーに届いた
2回301+no-storeCache-Control で覚えさせないようにできた
返した応答 2回開いてサーバーに届いた回数 読み取れること
301(Cache-Control なし) 1 恒久なので、ブラウザが覚えた
308(Cache-Control なし) 1 301と同じ
302(Cache-Control なし) 2 一時なので、覚えない
307(Cache-Control なし) 2 302と同じ
301+Cache-Control: no-store 2 覚えさせないようにできた
301+Cache-Control: max-age=60 1 60秒は覚える
302+Cache-Control: max-age=60 1 302でも、指定すれば覚える

これが問題になるのは、まちがった301を出してしまい、あとからサーバーの設定を直した場合です。一度その URL を開いたブラウザは、設定を直したあとも、覚えた転送先へ移り続けます。

1試すときは302から

設定を試している間は302(または307)で返し、動きが確かめられてから301・308に変える。

2期限を付ける

301でも Cache-Control: max-age=3600 のように期限を付けると、まちがえても期限が切れれば直る。

3覚えた転送を消す

シークレットウィンドウで開き直すか、ブラウザのキャッシュを消す。開発者ツールの「キャッシュを無効にする」も使える。

Cache-Control の書き方は、関連記事「Cache-Controlのno-cacheとno-storeの違いと正しい設定」で扱っています。

meta refresh・JavaScriptとの違い(HTMLだけで転送する)

この節では、サーバーの設定を変えずに HTML や JavaScript で転送する方法と、HTTP のリダイレクトとの違いが、時間と履歴の実測で分かります。

サーバーの設定を変えられないときは、ページの中に転送の指示を書けます。 ただし、ページをいったん読み込んでから移るため、HTTP のリダイレクトより遅く、検索エンジンの扱いも違います。

<!-- meta refresh:0秒後に /landing へ -->
<meta http-equiv="refresh" content="0; url=/landing">

<!-- JavaScript:履歴に残す -->
<script>location.href = "/landing";</script>

<!-- JavaScript:履歴を置き換える -->
<script>location.replace("/landing");</script>

HTTP のヘッダーで Refresh: 0; url=/landing を返す方法もあります。/start を開いたあと、それぞれのページを開いて /landing に着くまでの時間と、「戻る」を押したときの行き先を Chromium で測りました。

方法 /landing まで 「戻る」の行き先 Google 検索での扱い(公式)
HTTP 301 185ms /start(転送元は履歴に残らない) 恒久
meta refresh 0秒 135ms /start 恒久
Refresh ヘッダー 0秒 133ms /start 恒久
meta refresh 3秒 3,124ms /meta3(転送元に戻る) 一時
location.href(読み込み中) 175ms /start 恒久(ただし推奨しない)
location.replace 230ms /start 同上
location.href(1秒後) 1,188ms /js-href-late(転送元に戻る) 同上
  • 時間は 127.0.0.1 の中で測った値です。実際の回線では、クライアント側の転送はページを読み込む分だけ遅れます。
  • 遅れて移る方法(meta refresh 3秒・1秒後の location.href)は、転送元が履歴に残りました。「戻る」を押すと転送元に戻り、そこからまた転送されるため、戻れなくなったように見えます。すぐに移すなら、meta refresh は0秒、JavaScript は location.replace にします。
  • Chromium 153 では、読み込み中に実行した location.href も履歴を置き換えました。クリックなどの操作のあとに実行した場合とは結果が変わるので、確実に置き換えるなら replace を使います。

MDN は、複数の方法が重なったときの順番を、HTTP のリダイレクトが常に最初、次に JavaScript、最後に meta refresh と説明しています。そのうえで、可能な限り HTTP のリダイレクトを使い、<meta> を重ねて書かないよう勧めています。片方だけ直すと、転送先が食い違ったりループしたりするためです。

リダイレクトとSEO(Google検索セントラルの公式)

この節では、Google 検索がリダイレクトをどう扱うかを、Google 検索セントラルと Search Console ヘルプに書かれている範囲だけでまとめます。

Google は、URL を変えるときはできる限り「サーバー側の恒久的なリダイレクト」を使うよう勧めています。 Google 検索セントラル「リダイレクトと Google 検索」(2026年4月17日更新)の表を要約すると、次のとおりです。

種類 方法 Google での扱い
恒久 HTTP 301・308、meta refresh(0秒)、HTTP の Refresh(0秒)、JavaScript の location 転送先を正規 URL(代表の URL)を決めるシグナルとして使う。検索結果には転送先が出る
一時 HTTP 302・303・307、meta refresh(0秒より長い)、HTTP の Refresh(0秒より長い) 正規 URL のシグナルには使わない。検索結果には転送元が出る
  • JavaScript のリダイレクト:「サーバー側のリダイレクトまたは meta refresh リダイレクトを行えない場合にのみ」使うよう書かれています。レンダリング(ページの組み立て)に失敗すると、Google が転送を認識できないことがあるためです。
  • Googlebot が追う回数:「HTTP ステータスコード、ネットワーク エラー、DNS エラーが Google 検索に与える影響」(2026年3月6日更新)によると、Google のクローラーは既定で最大10回のリダイレクトをたどります。転送元から受け取った中身は無視され、最後の URL の中身が処理されます。307は302と、308は301と同じ扱いです。
  • チェーン(転送の連続):「URL の変更を伴うサイト移転」(2026年9月11日更新)は、最終的な宛先へ直接転送することを勧め、それができない場合は「チェーン内のリダイレクトの数を 5 個未満(理想的には 3 個以下)」に抑えるよう書いています。
  • 保つ期間:同じページに「リダイレクトをできるだけ長く保持します(一般的には 1 年以上)」とあります。

Search Console に出るリダイレクトの表示

Search Console ヘルプの「ページのインデックス登録レポート」は、次の2つを分けて説明しています。

表示 意味(公式の説明) 対応
ページにリダイレクトがあります 別のページへ転送する正規ではない URL なので、インデックスに登録されない 意図した転送なら、そのままでよい
リダイレクト エラー チェーンが長すぎる・ループしている・URL が最大長を超えた・チェーンに不正か空の URL がある curl でたどって原因を探す(後の節)
  • 1URL を戻さないなら301か308302のままにすると、検索結果に前の URL が出続ける
  • 2最終の URL へ1回で転送するhttp→https→www→新しいパス、の3段にしない。条件をまとめて1回で移す
  • 3サイト内のリンクを新しい URL に書き換える転送に頼らず、内部リンク・サイトマップ・canonical を新しい URL にそろえる
  • 4転送は1年以上残す外部のリンクやブックマークから来る人のためにも残す

nginxでリダイレクトを設定する(return・rewrite)

この節では、nginx でリダイレクトを書く方法と、書き方によって返るコードや Location がどう変わるかが、実際の応答で分かります。

nginx では return 301 URL; がいちばん分かりやすい書き方です。 手元で試した設定です(ログの場所などの行を省いています)。

server {                                  # 別ドメインからの一括転送
    listen 127.0.0.1:45451;
    server_name example.com;
    return 301 https://www.example.com$request_uri;
}
server {
    listen 127.0.0.1:45451 default_server;
    root /path/to/www;

    location = /old-301 { return 301 /new.html; }
    location = /bare    { return https://www.example.com/; }   # コードを省略
    rewrite ^/blog/(\d+)$ /news/$1 permanent;                  # 301
    rewrite ^/tmp/(\d+)$  /news/$1 redirect;                   # 302
    location = /keep-qs { return 301 /new.html$is_args$args; }
    location = /rel-off { absolute_redirect off; return 301 /new.html; }
}
開いた URL 応答 Location
/old-301 301 http://127.0.0.1:45451/new.html(絶対 URL に直された)
/bare(コードを省略) 302 https://www.example.com/
/blog/12(permanent) 301 /news/12
/tmp/12(redirect) 302 /news/12
/keep-qs?utm=1&a=2 301 /new.html?utm=1&a=2
/docs(フォルダー・末尾「/」なし) 301 /docs/(設定なしで自動)
/rel-off 301 /new.html(相対のまま)
Host: example.com で /a/b?x=1 301 https://www.example.com/a/b?x=1
  • return でコードを省略し、URL だけを書くと302になりました。 恒久の転送のつもりなら、301を必ず書きます。
  • return 301 /new.html; のように「/」から始まるパスを書くと、nginx は自分のホスト名とポートを付けた絶対 URL にして返しました。 手前にロードバランサーや別ポートの中継があると、内側のポート番号が Location に出ることがあります。absolute_redirect off; にすると、書いたとおりの相対パスで返りました。
  • rewrite は、元のクエリ文字列(? の後ろ)を自動で付けます。return で残したいときは $is_args$args か、パスとクエリを含む $request_uri を付けます。

どの location に当たるかの決まりは、関連記事「nginx location書き方|優先順位とtry_filesをcurlで検証」で扱っています。

Apache(.htaccess)でリダイレクトを設定する

この節では、Apache の .htaccess で書くリダイレクトの基本形と、省略したときのコード、https 化でループする典型例が分かります。

Apache には、単純な転送の Redirect(mod_alias)と、条件付きの転送ができる RewriteRule(mod_rewrite)があります。 手元の Apache 2.4.58 で試した .htaccess です。

# mod_alias
Redirect /a-default /new.html
Redirect 301 /a-301 /new.html
Redirect permanent /a-perm /new.html
RedirectMatch 301 ^/old/(.*)$ /new/$1

# mod_rewrite
RewriteEngine On
RewriteRule ^r-301$ /new.html [R=301,L]
RewriteRule ^r-plain$ /new.html [R,L]
RewriteRule ^r-308$ /new.html [R=308,L]
RewriteCond %{HTTP_HOST} ^example\.com$ [NC]
RewriteRule ^(.*)$ https://www.example.com/$1 [R=301,L]
開いた URL 応答 Location
/a-default(Redirect・コード省略) 302 /new.html
/a-301・/a-perm 301 /new.html
/old/x.html(RedirectMatch) 301 /new/x.html
/r-301(R=301) 301 /new.html
/r-plain(R だけ) 302 /new.html
/r-308(R=308) 308 /new.html
Host: example.com で /a/b?x=1 301 https://www.example.com/a/b?x=1

Apache でも、コードを省略した Redirect と [R] は302でした。 MDN も、mod_alias の Redirect は既定で302を返すと説明しています。

ロードバランサーの後ろで https 化するとループする

https の暗号化をロードバランサーや CDN が受け持ち、Apache には http で届く構成では、Apache から見ると毎回「https ではない」ため、何度でも https へ転送してループします。手元では、https の通信を受けた中継が付けるヘッダー X-Forwarded-Proto: https を curl で付けて、その状況を再現しました。

# ループする書き方
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]

# 中継のヘッダーも見る書き方
RewriteCond %{HTTPS} off
RewriteCond %{HTTP:X-Forwarded-Proto} !https
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
書き方 ヘッダーなし(http で来た) X-Forwarded-Proto: https
HTTPS だけを見る 301 → https 301 → https(ループの原因)
中継のヘッダーも見る 301 → https 200

X-Forwarded-Proto はクライアントが自由に付けられるヘッダーなので、信頼してよいのは、自分の中継を通ったリクエストだけです。https 化・www の統一・ディレクトリの移動などのコピーして使える書き方は、関連記事「.htaccessの書き方|リダイレクト・https化・アクセス制限のコピペ集」で扱っています。

Next.js・Express・PHPのリダイレクトと既定のコード

この節では、アプリのコードからリダイレクトするときの書き方と、何も指定しないと何番が返るのかが、実行結果で分かります。

フレームワークごとに「何も指定しないときのコード」が違います。 恒久の転送のつもりで既定のまま使うと、一時的な転送になります。

書き方 返ったコード 補足
Next.js redirects()・permanent: true 308 Refresh: 0;url=/new も付いた
Next.js redirects()・permanent: false 307
Next.js redirects()・statusCode: 301 301 301を明示したいとき
Next.js redirect()(Server Component) 307 permanentRedirect() は308
Next.js proxy.js の NextResponse.redirect() 307 Next.js 16 で middleware から改名
Express 5 res.redirect('/new') 302 res.redirect(301, '/new') で301
PHP header('Location: /new') 302 POST に対しても302。第3引数で301

Next.js(next.config.mjs と redirect())

// next.config.mjs
export default {
  async redirects() {
    return [
      { source: '/cfg-perm', destination: '/new', permanent: true },   // 308
      { source: '/cfg-temp', destination: '/new', permanent: false },  // 307
      { source: '/cfg-301', destination: '/new', statusCode: 301 },     // 301
      { source: '/articles/:slug', destination: '/blog/:slug', permanent: true },
    ];
  },
};
// app/old/page.js
import { redirect } from 'next/navigation';
export const dynamic = 'force-dynamic';
export default function Page() { redirect('/new'); }
$ curl -si http://127.0.0.1:45454/cfg-perm
HTTP/1.1 308 Permanent Redirect
location: /new
Refresh: 0;url=/new

/new
$ curl -s -o /dev/null -w '%{http_code} %{redirect_url}\n' http://127.0.0.1:45454/articles/hello
308 http://127.0.0.1:45454/blog/hello
$ curl -si http://127.0.0.1:45454/old
HTTP/1.1 307 Temporary Redirect
location: /new
Cache-Control: private, no-cache, no-store, max-age=0, must-revalidate

Next.js は、恒久の転送に301ではなく308を使います。308は POST を POST のまま転送するので、フォームや API の URL を移しても中身が消えません。redirect() の307には no-store が付いていて、ブラウザに覚えられない形でした。proxy.js(以前の middleware)の使い方は、関連記事「Next.js proxy.ts入門|middleware移行とmatcher実例」で扱っています。

Express 5

const express = require('express');
const app = express();
app.get('/old', (req, res) => res.redirect('/new'));            // 既定は302
app.get('/moved', (req, res) => res.redirect(301, '/new'));     // 恒久
app.post('/form', (req, res) => res.redirect(303, '/done'));    // POST のあと
app.get('/back', (req, res) => res.redirect('back'));
app.listen(45453, '127.0.0.1');
$ curl -si http://127.0.0.1:45453/old
HTTP/1.1 302 Found
Location: /new
Content-Type: text/plain; charset=utf-8

Found. Redirecting to /new
$ curl -si http://127.0.0.1:45453/back
HTTP/1.1 302 Found
Location: back

Express 5.3.0 では、res.redirect('back') は Referer の URL にならず、Location: back という文字のまま返りました。

PHP

<?php
header('Location: /new');                 // 302
exit;

header('Location: /new', true, 301);      // 301
exit;

header() のあとに exit; を書かないと、転送の応答のあとも処理が続きます。手元の PHP 8.3.6 では、POST のリクエストに対しても既定は302でした。

リダイレクトループ(ERR_TOO_MANY_REDIRECTS)の原因と直し方

この節では、「リダイレクトが繰り返し行われました」と表示されたときに、何が起きていて、どこを直すかが分かります。

ERR_TOO_MANY_REDIRECTS は、転送の先がまた転送になり、いつまでもページにたどり着かない状態です。 手元で A→B→A と転送し合う URL を作り、Chromium(日本語の画面)で開いた表示です。

このページは動作していません
127.0.0.1 でリダイレクトが繰り返し行われました。
Cookie を削除してみてください.
ERR_TOO_MANY_REDIRECTS
再読み込み

Playwright の page.goto では net::ERR_TOO_MANY_REDIRECTS、curl では次のエラーになりました。

$ curl -sSL http://127.0.0.1:45451/loop-a -o /dev/null
curl: (47) Maximum (50) redirects followed
$ curl -sSL --max-redirs 5 http://127.0.0.1:45451/loop-a -o /dev/null
curl: (47) Maximum (5) redirects followed
試したこと 結果
Chromium でループする URL を開く サーバーに届いたのは20回。そこでエラー画面になった
Chromium で19回転送したあとにページへ着く URL 200で表示できた
Chromium で20回転送したあとにページへ着く URL ERR_TOO_MANY_REDIRECTS
curl -L(既定) 50回たどって終了コード47
Googlebot(公式の記述) 既定で最大10回までたどる

ループでなくても、転送が長く続けばブラウザは同じエラーになります。 Googlebot は10回までなので、Google の勧める「5個未満(理想的には3個以下)」に収めておけば、どちらにも引っかかりません。

ループの主な原因

原因 よく出る場面 直し方
中継の後ろで https 化の条件が常に成り立つ CDN・ロードバランサーで https を受け、サーバーには http で届く X-Forwarded-Proto も条件に入れる(手元で再現)
www あり・なしの転送を2か所で逆向きに設定 サーバーの設定と CMS・CDN の設定がぶつかる どちらか1か所だけにする
ログイン画面自体がログインを求める 認証の対象から /login を外し忘れた ログイン画面・静的ファイルを対象から外す
Cookie の状態が食い違う 言語や地域の Cookie で振り分ける・ログイン情報が壊れた その Cookie を消すと直るか確かめる
以前の301がブラウザに残っている 設定を直したのに自分のブラウザだけループする シークレットウィンドウで確かめ、キャッシュを消す

直す手順

1

シークレットウィンドウで開く

Cookie や以前の301の記憶を使わずに開ける。シークレットでは開けるなら、原因は自分のブラウザに残った情報。Cookie とキャッシュを消す。

見ている人もできる

2

curl で転送の道筋を全部出す

curl -sIL --max-redirs 10 URL で、各段のステータスと Location を並べる。同じ URL が2回出たら、そこがループの輪。

作る人

3

どこが返しているかを見分ける

Server ヘッダー・ログで、転送を返しているのが CDN・Web サーバー・アプリのどれかを確かめる。2か所が互いに逆の転送をしていることが多い。

作る人

4

転送の規則を1か所にまとめる

https 化・www・末尾「/」の規則は、1つの層だけに書く。直したら302で試し、確かめてから301に戻す。

作る人

リダイレクトの確かめ方(curl・開発者ツール・Search Console)

この節では、設定したリダイレクトが意図どおりに動いているかを確かめる方法が、コマンドと出力で分かります。

いちばん確実なのは curl です。 ブラウザと違って転送を覚えないので、毎回サーバーの今の設定を見られます。

$ curl -sIL http://127.0.0.1:45452/chain/3
HTTP/1.1 301 Moved Permanently
Location: /chain/2
HTTP/1.1 301 Moved Permanently
Location: /chain/1
HTTP/1.1 301 Moved Permanently
Location: /landing
HTTP/1.1 200 OK

$ curl -sL -o /dev/null -w '%{http_code} %{num_redirects} %{url_effective}\n' http://127.0.0.1:45452/chain/4
200 4 http://127.0.0.1:45452/landing

$ curl -s -o /dev/null -w '%{http_code} -> %{redirect_url}\n' http://127.0.0.1:45451/old-301
301 -> http://127.0.0.1:45451/new.html
  1. 11段だけ見る:curl -sI URLステータスと Location が想定どおりか
  2. 2最後までたどる:curl -sIL URL何段で着くか・途中に302が混ざっていないか
  3. 3回数と着いた先:-w '%{num_redirects} %{url_effective}'たくさんの URL をまとめて確かめるときに便利
  4. 4Host を変えて試す:-H 'Host: example.com'DNS を切り替える前に、ドメインごとの転送を確かめられる
  5. 5ブラウザで見る:開発者ツールの Network「ログを保持」をオンにすると、転送前のリクエストも残る
  6. 6検索での扱いを見る:Search Consoleページのインデックス登録レポートと URL 検査
  • 開発者ツール:Chrome for Developers の公式ドキュメントによると、Network パネルの「ログを保持」をオンにすると、ページの読み込みをまたいでリクエストが残ります。「キャッシュを無効にする」をオンにすると、ブラウザのキャッシュを使わずに読み込みます。覚えた301に邪魔されずに確かめるときに使います。
  • 転送先の Referer とフラグメント:手元の Chromium では、リンクをクリックして301で転送されたとき、転送先にも最初のページの Referer が届きました。/frag#section-2 を開いて301で転送されたときは、着いた URL が /landing#section-2 になり、「#」以降が引き継がれました。

どのリダイレクトを使うか:判断表と、やってはいけない転送

この節では、状況ごとに使うコードと方法が一目で分かり、事故になりやすい転送の避け方が分かります。

状況 使うもの 理由
ページの URL を変えた・ドメインを移した 301(または308) 恒久。検索結果が転送先に切り替わる
http を https に、www なしを www ありに 301(または308) 同じページを1つの URL にそろえる
POST を受ける API の URL を移した 308 POST と本文を保つ
フォーム送信のあと、完了ページを見せる 303 再読み込みで二重に送られない
ログインしていない人をログイン画面へ 302(API なら401を返す) 一時的。ログイン後に元へ戻す
メンテナンス中だけ別ページへ 302・307(または503を返す) 一時的。元の URL を検索結果に残す
サーバーの設定を変えられない meta refresh 0秒 Google は0秒の meta refresh を恒久として扱う
ページを消し、代わりのページが無い 転送しない(404・410) 関係の無いページへ移すと利用者が迷う
同じ内容の URL が並ぶが、どちらも見せたい canonical タグ 転送せずに、代表の URL を伝える

ログイン後の戻り先を URL で受け取る

やってはいけない?next= に書かれた URL へ、確かめずにそのまま転送する。外部のサイトへ移せてしまう(オープンリダイレクト)
→
代わりに自分のサイトのパス(「/」で始まり、「//」で始まらない)だけを許す

手元の Express では、/mypage は通し、//evil.example と https://evil.example は「/」へ戻した

URL をまとめて変えたとき

やってはいけない前の URL を全部トップページへ転送する
→
代わりに1ページずつ、内容が対応する新しい URL へ転送する。対応先が無いページは404・410

探していた情報にたどり着けないと、利用者はすぐに離れる

オープンリダイレクトを防ぐ判定は、手元では次のように書きました。

app.get('/go', (req, res) => {
  const next = String(req.query.next || '/');
  const ok = next.startsWith('/') && !next.startsWith('//') && !next.includes('\\');
  res.redirect(ok ? next : '/');
});
next=/mypage -> 302 http://127.0.0.1:45453/mypage
next=//evil.example -> 302 http://127.0.0.1:45453/
next=https://evil.example -> 302 http://127.0.0.1:45453/

「//」で始まる値は、ブラウザでは「同じ https・http のまま別のドメイン」という意味になるため、「/」で始まるかだけを見ると素通りします。

よくあるエラー・つまずき(原文つき)

この節では、リダイレクトの設定や確認でよく出る表示と、その原因・直し方がまとめて分かります。

出た表示・起きたこと 原因 直し方
127.0.0.1 でリダイレクトが繰り返し行われました。(ERR_TOO_MANY_REDIRECTS) 転送のループ、または20回以上の転送 curl -sIL で輪を探し、規則を1か所にまとめる
curl: (47) Maximum (50) redirects followed 同上(curl の上限は既定で50) 同上。--max-redirs で上限を変えて調べる
TypeError: Failed to fetch(fetch に redirect: 'error') 転送を禁止した fetch が3xx を受けた API の URL を最終の URL に直す
フォームを送ったのに中身が空で届く 301・302・303で POST が GET に変わった 送信先を最終の URL にする。移すなら307・308
設定を直したのに前の転送先へ飛ぶ 301がブラウザにキャッシュされている シークレットウィンドウ・キャッシュの削除で確かめる
Location にポート番号が付いて開けない nginx が「/」から始まるパスを絶対 URL に直した absolute_redirect off; か、完全な URL を書く
Search Console に「リダイレクト エラー」 チェーンが長い・ループ・URL が長すぎる・不正な URL 最終の URL へ直接転送する
「戻る」で戻れない 遅れて移る meta refresh・JavaScript が転送元を履歴に残した HTTP の転送か、0秒・location.replace にする

よくある質問

この節では、リダイレクトについてよく調べられる疑問に、短く答えます。

Q. リダイレクトとは簡単にいうと何ですか? A. ある URL を開いたときに、自動で別の URL へ移す仕組みです。サーバーが「3xx のステータスコード」と「移動先(Location)」を返し、ブラウザがその URL を開き直します。

Q. 301と302はどう使い分けますか? A. URL をもう戻さないなら301、あとで元に戻すなら302です。Google は301を転送先を正規 URL とする強いシグナル、302を弱いシグナルとして扱い、302では検索結果に転送元の URL が残ります。

Q. 307と308はいつ使いますか? A. POST などの GET 以外のリクエストを、メソッドと本文を保ったまま転送したいときです。307は一時、308は恒久です。手元の Chromium では、301・302・303では POST が GET に変わり本文が消え、307・308では POST のまま本文も届きました。

Q. Linux の「リダイレクト」とは別のものですか? A. 別のものです。シェルの > や 2>&1 は、コマンドの出力の行き先を変える機能です。関連記事「Linuxリダイレクト 2>&1の意味と順序|標準エラー出力・tee」で扱っています。

Q. リダイレクトするとSEOの評価は引き継がれますか? A. Google 検索セントラルは、URL を変えるときはサーバー側の恒久的なリダイレクト(301・308)を勧め、恒久の転送では転送先を正規 URL のシグナルとして使うと説明しています。転送は一般に1年以上保つよう書かれています。

Q. リダイレクトの設定が反映されないのはなぜですか? A. ブラウザが以前の301を覚えていることが多いです。curl で確かめるか、シークレットウィンドウで開きます。curl でも変わらなければ、設定の再読み込み忘れ、CDN のキャッシュ、別の規則が先に当たっていないかを見ます。

Q. meta refresh は使ってはいけませんか? A. サーバーの設定を変えられないときに使えます。Google は0秒の meta refresh を恒久、0秒より長いものを一時の転送として扱います。手元では、3秒の meta refresh は転送元が履歴に残り、「戻る」で転送元に戻りました。

Q. リダイレクトと canonical タグはどう違いますか? A. リダイレクトは利用者も検索エンジンも別の URL へ移します。canonical タグは、ページはそのまま見せたうえで、検索エンジンに代表の URL を伝えるだけです。両方の URL を見せる必要が無ければリダイレクト、必要があれば canonical を使います。

まとめ

  • リダイレクトは、サーバーが3xx と Location を返し、ブラウザが別の URL を開き直す仕組み。サーバー側の転送がいちばん確実。
  • 恒久は301・308、一時は302・303・307。手元の Chromium では、301・302・303で POST が GET に変わり本文が消え、307・308は POST のまま本文も届いた。
  • Cache-Control の無い301・308はブラウザに覚えられ、2回目はサーバーに届かなかった。試すときは302から、または max-age を付ける。
  • nginx の return・Apache の Redirect・Express・PHP はコードを省略すると302、Next.js は恒久が308・一時が307。恒久のつもりならコードを必ず書く。
  • ループすると Chromium は20回目の転送で ERR_TOO_MANY_REDIRECTS になった。Google は最終の URL へ直接、無理でも5個未満(理想は3個以下)、転送は一般に1年以上保つよう勧めている。

参考・出典

確認日はいずれも 2026年10月29日です。

  • Google 検索セントラル「リダイレクトと Google 検索」(2026年4月17日更新):https://developers.google.com/search/docs/crawling-indexing/301-redirects
  • Google 検索セントラル「HTTP ステータスコード、ネットワーク エラー、DNS エラーが Google 検索に与える影響」(2026年3月6日更新):https://developers.google.com/search/docs/crawling-indexing/http-network-errors
  • Google 検索セントラル「URL の変更を伴うサイト移転」(2026年9月11日更新):https://developers.google.com/search/docs/crawling-indexing/site-move-with-url-changes
  • Search Console ヘルプ「ページのインデックス登録レポート」:https://support.google.com/webmasters/answer/7440203
  • MDN「HTTP のリダイレクト」(2026年4月12日更新):https://developer.mozilla.org/ja/docs/Web/HTTP/Guides/Redirections
  • IETF「RFC 9110 HTTP Semantics」15.4 Redirection 3xx:https://www.rfc-editor.org/rfc/rfc9110#section-15.4
  • Chrome for Developers「ネットワーク機能リファレンス」:https://developer.chrome.com/docs/devtools/network/reference
  • nginx「Module ngx_http_rewrite_module」:https://nginx.org/en/docs/http/ngx_http_rewrite_module.html
  • nginx「Module ngx_http_core_module」(absolute_redirect):https://nginx.org/en/docs/http/ngx_http_core_module.html#absolute_redirect
  • Apache HTTP Server「mod_alias」:https://httpd.apache.org/docs/2.4/mod/mod_alias.html
  • Apache HTTP Server「mod_rewrite」:https://httpd.apache.org/docs/2.4/mod/mod_rewrite.html
  • Next.js「redirects」:https://nextjs.org/docs/app/api-reference/config/next-config-js/redirects
  • Next.js「redirect」:https://nextjs.org/docs/app/api-reference/functions/redirect
  • Express「res.redirect()」:https://expressjs.com/en/5x/api.html#res.redirect
  • PHP マニュアル「header」:https://www.php.net/manual/ja/function.header.php

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「リダイレクトとは|301・302・307・308の違いとPOST・キャッシュを実測」

株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?