この内容は古いバージョンです。最新バージョンを表示するには、戻るボタンを押してください。
バージョン:9
ページ更新者:guest
更新日時:2026-06-11 07:12:00

タイトル: リダイレクト(header)
SEOタイトル: PHP リダイレクト (header Location) 完全ガイド(301/302/303/307/308 / exit / 出力前必須)

この記事の要点
  • header("Location: /path"); exit; が PHP リダイレクトの基本形
  • 必ず exit; を付ける。後続コードが実行されてしまうとセキュリティ事故に
  • HTTP ステータスを意識: 301 (恒久) / 302 (一時) / 303 (POST 後 GET) / 307 (一時、メソッド保持) / 308 (恒久、メソッド保持)
  • 出力前に呼ぶ — 1 行でも echo / 空白 / BOM があると Cannot modify header information エラー
  • 出力バッファ (ob_start()) で回避できるが、根本対策は出力前に header() を呼ぶ
  • Laravel なら return redirect()->route("home"); でフレームワーク管理

基本構文

<?php
// 最小形 (デフォルト 302 Found)
header('Location: /thanks.php');
exit;

必ず exit; (または die;) を付けてください。 Location ヘッダーを送ってもブラウザがリダイレクトする前に、PHP は残りのコードを最後まで実行します。認証チェックの直後でリダイレクトしているつもりが、続けて機密処理を実行してしまう事故が頻発します。

// ❌ 危険: リダイレクト後に処理が継続する
if (!isLoggedIn()) {
    header('Location: /login');
    // exit; を忘れている
}
processDelete($_GET['id']);  // → 未ログインでも削除が走る!

// ✅ 正しい: exit で確実に止める
if (!isLoggedIn()) {
    header('Location: /login');
    exit;
}
processDelete($_GET['id']);

HTTP ステータスの使い分け

コード意味メソッド変換用途
301Moved Permanently (恒久)POST → GET に変わるURL 変更、ドメイン引越し、HTTP → HTTPS
302Found (一時)POST → GET に変わる古い実装の既定。302 でなく 303 推奨
303See OtherPOST → GET (明示的)POST 完了 → GET 結果画面 (PRG パターン)
307Temporary Redirectメソッド維持POST のまま別 URL で受け直したい
308Permanent Redirectメソッド維持恒久的な URL 変更 + メソッド保持
<?php
// 恒久リダイレクト (SEO 観点で重要)
header('Location: https://example.com/new-path', true, 301);
exit;

// 一時リダイレクト
header('Location: /maintenance.php', true, 302);
exit;

// POST 後の Get-Redirect-After-Post (PRG パターン)
header('Location: /result.php?id=' . $newId, true, 303);
exit;

// PHP 8 以降の関数
http_response_code(301);
header('Location: /new-path');
exit;

「Cannot modify header information」エラー

PHP 最大の頻出エラーの一つ:

Warning: Cannot modify header information - headers already sent by
(output started at /var/www/index.php:12) in /var/www/login.php on line 25

原因はheader() を呼ぶ前にすでに何かが出力されていること。次のいずれか:

  1. <?php前に空白や HTMLがある
  2. BOM 付き UTF-8 で保存されている (先頭 3 バイトの不可視 BOM)
  3. echo / print / var_dump をすでに実行している
  4. include 先のファイル末尾に空行がある
  5. PHP の Warning / Notice がすでに出力されている

対処

<?php
// 1. ファイル先頭の空白を削除
// 2. ?> を省略する (PHP 専用ファイルでは閉じタグ不要)
// 3. BOM なし UTF-8 で保存
// 4. include ファイルの末尾も同様に注意

// 緊急回避: 出力バッファリング
ob_start();
echo "ここで出力されても OK (バッファに溜まる)";
header('Location: /target');
ob_end_clean();   // バッファ破棄
exit;

絶対 URL vs 相対 URL

HTTP 1.1 仕様上、Location ヘッダーは絶対 URL が原則ですが、HTTP 1.1 以降は相対 URL も受け入れます。可搬性を優先するなら絶対 URL:

<?php
// 絶対 URL を組み立てる
$scheme = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http';
$host = $_SERVER['HTTP_HOST'];
$path = '/dashboard';

header("Location: $scheme://$host$path", true, 302);
exit;

// 簡易ヘルパー
function redirect(string $path, int $code = 302): void {
    $scheme = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http';
    $host = $_SERVER['HTTP_HOST'] ?? 'localhost';
    header("Location: $scheme://$host$path", true, $code);
    exit;
}

redirect('/login', 302);

JavaScript リダイレクト

PHP 出力後にどうしてもリダイレクトしたい場合の最終手段:

<script>
  // 履歴に残らない (replace)
  location.replace('/target');

  // 履歴に残る (戻るボタンで戻れる)
  location.href = '/target';
</script>

<!-- メタリフレッシュ (SEO 評価が下がるので非推奨) -->
<meta http-equiv="refresh" content="0; url=/target">

<!-- 3 秒後にリダイレクト -->
<meta http-equiv="refresh" content="3; url=/target">

セキュリティ: オープンリダイレクト脆弱性

ユーザー入力をそのまま Location に使うのは危険:

<?php
// ❌ 危険: 外部 URL に飛ばせるオープンリダイレクト
$next = $_GET['next'] ?? '/';
header("Location: $next");
exit;
// → /login?next=https://evil.com で攻撃可能

// ✅ ホワイトリスト方式
$allowed = ['/dashboard', '/mypage', '/cart', '/'];
$next = $_GET['next'] ?? '/';
if (!in_array($next, $allowed, true)) {
    $next = '/';
}
header("Location: $next");
exit;

// ✅ 同一ドメイン制約
$next = $_GET['next'] ?? '/';
if (preg_match('#^/(?!/)#', $next) !== 1) {  // / で始まり // で始まらない
    $next = '/';
}
header("Location: $next");
exit;

Laravel での書き方

// シンプルなリダイレクト
return redirect('/home');

// 名前付きルート
return redirect()->route('dashboard');

// 直前のページに戻る (フォームエラー時)
return back()->withInput()->withErrors($errors);

// 外部 URL
return redirect()->away('https://example.com');

// HTTP ステータス指定
return redirect('/login', 302);
return redirect()->route('home', [], 301);

// アクション直接呼び出し
return redirect()->action([UserController::class, 'index']);

// セッションへフラッシュメッセージ付与
return redirect('/dashboard')->with('status', '保存しました');

FAQ

Q: 301 と 302 はどう違う?
A: 301 は恒久。検索エンジンが古い URL を捨てて新 URL を採用、ブラウザもキャッシュする。一時的変更なら 302/303 を使う。SEO 引越しは必ず 301。

Q: POST フォーム送信後のリダイレクトはどれ?
A: 303 See Other が正解。302 だと再 POST の可能性がある (古いブラウザ)。PRG パターン (Post-Redirect-Get) で二重送信防止。

Q: クッキーをセットしつつリダイレクトしたい
A: setcookie()出力前に呼ぶ。setcookie('key', $val); header('Location: /'); exit; の順で OK。