HTTP 302 Foundとは?ステータスコード302の意味と301との違い
ポイント
HTTP 302 Foundは一時的なリダイレクトです。サーバーは「今は別のURLにある」と伝え、そのURLをLocationヘッダーで示します。ブラウザはすぐにそのURLを読み込みますが、リンクや検索エンジンは元のURLを使い続けます。永続的な移転には301か308を使ってください。私たちが確認した大手サイト20件で、そうした移転に302を使っていたところはありませんでした。302に出会うのは、たいていログインが必要なページです。ブラウザは302をたどるときにPOSTをGETに変えるため、メソッドを保つなら307、フォーム送信の後なら303を使います。
今すぐ確認

読み始める
HTTP 302ステータスコードとは?
HTTP 302 Foundは一時的なリダイレクトです。 サーバーは「リクエストされたものは今は別の場所にある」と応答し、その移動先のURLをLocationヘッダーで示します。ブラウザはそのURLをすぐに読み込みますが、ブックマークやリンク、検索エンジンは元のURLを使い続けるべきとされています(RFC 9110 §15.4.3)。
この「今は」という点が、古いURLが完全になくなったことを示す301 Moved Permanentlyとの違いのすべてです。HTTP/1.0では302は「Moved Temporarily」と呼ばれていました(RFC 1945)。1999年にRFC 2616で「Found」に改名されましたが、意味は変わっていません。
訪問者として302を目にすることはほとんどありません。画面に何かが表示される前に、ブラウザがたどってしまうからです。302に出会うのは、DevTools、curl、APIクライアント、SEO監査ツールの中です。
すべてのリダイレクトとステータスコードを確認
302レスポンスの構造
302レスポンスはシンプルで、次の要素で構成されます。
- ステータスライン —
HTTP/1.1 302 Found、または単にHTTP/2 302(HTTP/2には「Found」のような理由句がありません) - Location — 移動先のURL。RFC 9110では、サーバーはこのヘッダーを送るべき(SHOULD)とされています。ない場合、ブラウザは移動先がわからず302自体のボディを表示し、たいていは空白です
- ボディ — RFC 9110によれば「通常はリンクを含む短いハイパーテキストの注記」が入ります。ブラウザはこれを無視します。nginx、Apache、Expressはデフォルトで1行の注記を送り、大手サイトの多くは空のボディを返します
- キャッシュ関連ヘッダー — 不要です。
Cache-Control: max-ageやExpiresを付けない限り、ブラウザは302をキャッシュしません。一方、301と308はデフォルトでキャッシュされます。ただしCDNは302をキャッシュすることがあります(後述の「消えない302」を参照)
HTTP/1.1 302 Found
Location: https://www.example.com/login?next=%2Fdashboard
Content-Length: 0
Date: Mon, 26 Oct 2026 14:00:00 GMTLocationには完全なURLも、/loginのような相対パスも使えます。RFC 9110はあらゆるURI参照を認めており、ブラウザはリクエストしたURLを基準に解決します。アプリのコード(Expressなど)では、相対パスにするとプロキシの内側から内部ホスト名やhttp://が漏れるのを防げます。nginxとApacheは、absolute_redirect offやRedirectRelative Onを設定しない限り、いずれにせよ絶対URLに変換して返します。
200ではなく302が返ってくるのはなぜ?
ページが求めるものがリクエストに含まれていないか、使ったURLがサイトの提供しているURLと違うからです。よくある原因は次のとおりです。
- ログインしていない — ログインが必要なページは、ログインページへの302を返し、見ようとしたページを戻り先のパラメーターに入れます。ログアウトした状態で
github.com/settingsを開くと、/login?return_to=…に移動します。curlやAPIクライアントでは、セッションCookieかトークンを送ってください。 - クライアントがリダイレクトをたどらない — ブラウザ、
fetch()、Pythonのrequestsは自動でたどります。curlは-Lを付けないとたどらず、302で止まってそのボディだけを出力します。たいていは何も表示されません(ステータスとLocationを見るには-iを付けます)。 - サイトが提供しているURLではない — 末尾のスラッシュの有無、言語別のパス、別の場所へ転送するトラッキングリンクなどです。Expressの
res.redirect()、URLだけを指定したnginxのreturn、ApacheのRedirectは、別のコードを指定しない限りどれも302を返します。 - サーバーの手前で何かが追加している — オリジンを直接テストすると200なのに、公開URLでは302になる場合は、CDN、ロードバランサー、プロキシのルールがリダイレクトしています。
APIはログインがない場合、リダイレクトではなく401 Unauthorizedを返すのが一般的です。すべてのホップとステータスコードを確認するには、URLをリダイレクトチェッカーにかけてください。
302 vs 301 vs 307 vs 308:どのリダイレクトを使うべきか?
ほぼすべてのケースは5つのリダイレクトコードで対応できます。違いは2点、移動が永続的かどうかと、POSTがPOSTのままかどうかです。
| コード | 永続的? | ブラウザでのPOST | デフォルトでキャッシュ? | 最適な用途 |
|---|---|---|---|---|
| 301 Moved Permanently | はい | GETになる | はい | 永続的なURL変更、ドメイン移転 |
| 302 Found | いいえ | GETになる | いいえ | ログイン、短期間の移動、A/Bテスト |
| 303 See Other | いいえ | GETになる | いいえ | フォーム送信後に表示するページ |
| 307 Temporary Redirect | いいえ | POSTのまま | いいえ | メソッドを保つ必要がある一時的な移動 |
| 308 Permanent Redirect | はい | POSTのまま | はい | メソッドを保つ必要がある永続的な移動 |
303と307は302がきっかけで生まれたコードです。1997年にHTTP/1.1で303が追加され、1999年のRFC 2616は「既存のユーザーエージェント実装の大半は302を303レスポンスのように扱っている」と認めたうえで、307を追加し、302を「Found」に改名しました(RFC 2616 §10.3.3)。一時的なリダイレクトがPOSTを受け取る可能性があるなら、POSTを保つには307を、次のページをGETで読み込むには303を使ってください。
302リダイレクトを使うべきタイミング
リダイレクトがいずれなくなる見込みのときに302を使います。代表的な例は次のとおりです。
- ログインリダイレクト — 未ログインのユーザーを
/dashboardから/loginへ送り、ログイン後に戻す - A/Bテスト — 訪問者の一部をテスト用のページへ送る。Googleのテストに関するガイドも、この場合は301ではなく302を使うよう勧めています(Google検索セントラル)
- 作り直し中のページ — ページを作り直している数日間だけ、訪問者をお知らせページへ送る
- 短期キャンペーンのURL — セール期間中だけ
/black-fridayを今年のセールページに向ける - メンテナンス — 訪問者向けに限ります。検索エンジンに対しては、計画的な停止中はRetry-After付きの503を返すほうが適切です
これらのどれかが永続的なものになったら、301に切り替えてください。
302ステータスコードの送信方法
ほとんどのサーバーやフレームワークでは、302を1行で送れます。以下の例はどれも302 FoundとLocationヘッダーを送ります。302に必要なのはこれだけです。
Nginx
nginxではreturnにコードを付けて使います。URLだけを指定したreturnも302を返します(nginxのドキュメント):
server {
listen 80;
server_name example.com;
# Temporary redirect (302 Found)
location /old-page {
return 302 https://example.com/new-page;
}
}Apache (.htaccess)
Apacheでは、Redirect(ステータスを指定しなければ302がデフォルト)か、[R=302,L]フラグ付きのRewriteRuleを使います:
# Simple temporary redirect
Redirect 302 /old-page https://example.com/new-page
# Or with mod_rewrite for pattern matching
RewriteEngine On
RewriteRule ^maintenance$ /maintenance.html [R=302,L]Node.js (Express)
Expressのres.redirect()は、ステータスコードを指定しない場合のデフォルトが302です:
// Temporary redirect (302 Found by default)
app.get('/dashboard', (req, res) => {
if (!req.user) {
return res.redirect('/login') // sends 302
}
// ...render dashboard
})
// Or be explicit:
res.redirect(302, '/login')302ステータスコードのテスト方法
いちばん手早いのは、ターミナルでcurlを使う方法です。ブラウザのキャッシュに邪魔されずに、ステータスコードとLocationヘッダーを確認できます。
# Status and headers, without following the redirect
curl -sI https://example.com/old-page
# -I sends HEAD, which some servers answer differently; this sends a real GET
curl -s -o /dev/null -D - https://example.com/old-page
# Expected:
# HTTP/2 302
# location: https://example.com/new-page
# Follow the whole chain and print each hop's status and Location
curl -sL -o /dev/null -D - https://example.com/old-page | grep -iE '^(HTTP/|location:)'ターミナルがない場合は、DNS Robotのリダイレクトチェッカーが当サーバーからすべてのホップ(301、302、303、307、308、meta refresh)をたどり、各ステータスコードと最終URLを表示します。HTTPヘッダーチェッカーでは、各ホップの生のヘッダーを「リダイレクト」タブで確認できます。
302リダイレクトとSEO
Googleは2種類のリダイレクトを別々の指示として扱います。一時的なリダイレクト(302、303、307)の場合、Googlebotはリダイレクトをたどりますが、Googleはそれをリダイレクト先が正規版であることを示すシグナルとしては使いません。そのため、通常はリダイレクト元のページが検索結果に残ります。ほかのシグナルがリダイレクト先を指していれば、リダイレクト先がインデックスに登録されることもあります(Google検索セントラル)。
永続的なリダイレクト(301、308)はその逆で、「新しいURLを表示してほしい」という意味です。つまり本当の問題は「302はPageRankを渡すのか?」ではなく、「検索結果にどのURLを表示したいか?」です。rel=canonicalタグも、そのほかのシグナルの一つです。ページがどのURLを宣言しているかはメタタグチェッカーで確認できます。
- 永続的な移動には301か308 — 新しいドメイン、新しいURL構造、ページの統合
- 一時的な移動には302 — ログイン、A/Bテスト、キャンペーン
- 永続的な移動を302のまま放置しない — Googleが古いURLを表示し続けることがあります
- チェーンは短く — ホップごとに往復が1回増えます。また、Googlebotはデフォルトで最大10回のリダイレクトホップをたどります(Google)
なぜ302はPOSTリクエストをGETに変えるのか
302でいちばん意外な点です。初期の仕様はクライアントがメソッドを変えることを一度も認めていませんでしたが、ブラウザは302を受けるとPOSTをGETに変えていました。現在では、ブラウザが実装しているFetch Standardがこの慣習を明文化しています。301か302を受けると、POSTはGETになり、ボディは破棄されます(Fetch Standard)。
そのため、ブラウザがPOST /submit-formを送り、サーバーが302 Foundを返すと、ブラウザは続けてGET /thank-youを送り、フォームのデータは失われます。ブラウザはPUTとDELETEはそのまま保ちます。ほかのクライアントは動作がまちまちで、Pythonのrequestsは302を受けるとHEAD以外のすべてのメソッドをGETに変え、curl -Lは301、302、303でPOSTをGETに変えます(--post302を付けると302でPOSTを保ちます)。
# What you send:
POST /submit-form HTTP/1.1
Host: example.com
Content-Type: application/x-www-form-urlencoded
name=Alice&email=alice@example.com
# Server responds with 302:
HTTP/1.1 302 Found
Location: /thank-you
# Browser follows with GET (form data dropped!):
GET /thank-you HTTP/1.1
Host: example.comリダイレクトで元のメソッドを保つ必要がある(POSTはPOSTのまま、PUTはPUTのまま)なら、307 Temporary Redirectを使ってください。意図的にボディを捨ててGETに切り替える(古典的なPOST/Redirect/GETパターン)なら、303 See Otherを使います。どちらも意味が明確ですが、302はそうではありません。
302の一般的な問題と修正方法
302が問題を起こすときは、たいてい次のどれかです。
- 200ではなく302が返る — ログインが必要なページ、リダイレクトをたどらないクライアント、URLの表記ゆれのいずれかです(前述の「200ではなく302が返ってくるのはなぜ?」を参照)
- Locationヘッダーのない302 — たどる先がないため、ブラウザは302自体のボディを表示し、たいていは空白です。ステータスを送る前に
Locationを設定してください - 自分自身を指す302 — リダイレクトループです。ルールが自分のリダイレクト先でも発動しています。足りない条件を追加してください
- データが消えるフォーム — POST → 302 → GETでボディが破棄されます。結果ページを意図的に表示するなら303、POSTを保つなら307を使います
- 消えない302 — サーバーが
Cache-Control: max-ageやExpiresを付けて送ったためにブラウザがキャッシュしたか、CDNがキャッシュしています(Cloudflareは、キャッシュ対象のコンテンツについて、ヘッダーで別の指定がない限り302を20分間保持します。Cloudflareのドキュメント)。これらのヘッダーを外し、CDNのキャッシュを削除して、ブラウザのキャッシュもクリアしてください - 本番だけで出る302 — たいていはCDN、ロードバランサー、プロキシのルールがリダイレクトを追加しています。オリジンを直接テストして確かめてください
302リダイレクトループの診断
リダイレクトループとは、URL AがBへ送り、BがAへ送り返す状態です。Fetch Standardが定める上限の20回を超えると、ChromeはERR_TOO_MANY_REDIRECTSを、Firefoxは「ページの自動転送設定が正しくありません」を表示して読み込みを止めます。
典型的な原因は、CloudflareのFlexible暗号化モードです。Cloudflareは通常のHTTPでサーバーに接続し、サーバーはHTTPをHTTPSへリダイレクトし、Cloudflareは再びHTTPで要求します(Cloudflareのドキュメント)。
# Trace the full redirect chain (limit to 10 hops to avoid infinite loops)
curl -ILs --max-redirs 10 https://example.com 2>&1 | grep -i 'HTTP/\|location:'
# Example of a loop:
# HTTP/2 302
# location: http://example.com/
# HTTP/1.1 302 Found
# Location: https://example.com/
# HTTP/2 302
# location: http://example.com/ <-- loop confirmedLocationに同じURLが何度も出てくる場合、つまり自分自身を指しているか2つのURLが交互に出てくる場合は、ループです(CloudflareのFlexibleモードでは、https://example.com/ が自分自身へリダイレクトします)。完全な修正手順はERR_TOO_MANY_REDIRECTSガイドをご覧ください。DNS Robotのリダイレクトチェッカーは当サーバーからチェーンを追跡し、ループする地点で止まります。
302ステータスコードのベストプラクティス
いくつかの習慣で、302の問題の大半は防げます。
- Locationヘッダーを必ず送る — ないと、何もリダイレクトをたどれません
- キャッシュ関連ヘッダーを付けない —
max-ageもExpiresもない302はキャッシュされません。それが望ましい状態です - プロキシの内側での絶対URLに注意 — アプリのコードでは相対パスの
/loginにすると、内部ホスト名やhttp://がLocationに漏れません。nginxとApacheは自身の設定から絶対URLを組み立てます - 1ホップで済ませる —
A → Bは問題ありませんが、A → B → Cはすべての訪問者に往復を1回追加します - POSTを302で別のページへリダイレクトしない — 結果ページを表示するなら303、POSTを保つなら307を使います
- 古い一時リダイレクトを見直す — 理由がなくなった後も残っていることがよくあります。リダイレクトチェッカーで確認しましょう
- 移動が永続的になったらすぐ301に切り替える
ブラウザやツールは302をどう扱うか
どのクライアントも302をたどりますが、上限とメソッドの扱いが異なります。
| クライアント | 302を受けたときの動作 | リダイレクトの上限 |
|---|---|---|
| Chrome、Edge、Firefox、Safari | すぐにたどる。POSTはGETになる | 20(Fetch Standard) |
| JavaScriptのfetch() | デフォルトでたどる(redirect: "follow")。POSTの扱いは同じ | 20 |
| curl | たどらない。302のボディだけを出力し、たいてい空(ステータスとLocationを見るには-iか-Iを付ける) | なし(たどらない) |
| curl -L | たどる。301、302、303でPOSTはGETになる(--post301、--post302、--post303で保持) | 50(--max-redirs) |
| wget | たどる | 20(--max-redirect) |
| Python requests | たどる。HEAD以外のメソッドはすべてGETになる | 30 |
| Googlebot | たどる。リダイレクト元のURLを検索結果として保持 | デフォルトで10ホップ |
正確な定義は、RFC 9110の302の定義とMDNの302のページをご覧ください。
リダイレクトチェーンを数秒で追跡
DNS Robotの無料リダイレクトチェッカーで、リダイレクトチェーンの各ホップを確認できます。ステータスコード、Locationヘッダー、最終的な移動先を中立なサーバーから表示します(ブラウザのキャッシュの影響なし)。
出典
- RFC 9110: HTTP Semantics, 302 Found, IETF
- Fetch Standard: HTTP-redirect fetch, WHATWG
- Redirects and Google Search, Google Search Central
- Configure cache by status code (default 20 minutes for 302), Cloudflare



