2026年6月3日
2026年6月3日
wp_redirect()が効かない原因と対処|exit忘れ・headers already sent・フックの時期
wp_redirect() は、WordPressでユーザーを別のURLへ移動させる関数です。呼び出すだけで移動しそうに見えますが、実際には「Locationヘッダーを送る」だけの関数なので、呼び出す場所や後ろの処理によっては、何も起きなかったり、別のページが表示されたりします。
この記事では、wp_redirect() が効かないときによくある原因と、その直し方を説明します(WordPress 6.9 のソースコードで確認、2026年10月時点)。
wp_redirect() がしていること
wp_redirect( $location, $status = 302, $x_redirect_by = 'WordPress' ) の処理は次のとおりです。
$locationが空なら何もせずfalseを返す(wp_redirectフィルターで空にされた場合も同じ)- ステータスコードが300〜399の範囲外なら、
wp_die()でエラー画面を表示して止まる X-Redirect-By: WordPressヘッダーとLocationヘッダーを送り、trueを返す
処理を終了させる機能はありません。そのため、呼び出したあとに exit; を書かないと、PHPはそのまま続きの処理を実行し、ページの本文まで出力します。
原因1:exit を書いていない
wp_redirect() のあとに処理が続くと、続きの処理でさらにリダイレクトやデータの保存が行われたり、テンプレートが出力されたりします。呼び出したら、すぐに exit; で終了します。
<?php
wp_safe_redirect( home_url( '/thanks/' ) );
exit;
原因2:出力が始まったあとに呼んでいる(headers already sent)
HTTPのヘッダーは、本文を1文字でも出力したあとには送れません。テンプレートファイルの途中(get_header() のあとなど)で wp_redirect() を呼ぶと、ヘッダーが送られず、リダイレクトされません。WP_DEBUG を有効にしていると、次のような警告が出ます。
Warning: Cannot modify header information - headers already sent by (output started at /path/to/wp-content/themes/mytheme/header.php:12)
output started at のあとに、最初に出力したファイルと行番号が表示されます。テンプレートの途中で呼んでいる場合は、次の「正しいフックで呼ぶ」の形に直します。
ファイルの先頭や末尾にある空白・改行、文字コードの BOM(UTF-8 の先頭に付く目に見えない3バイト)も「出力」になります。functions.php やプラグインのファイルは、PHPだけのファイルなら末尾の ?> を書かないようにし、文字コードは BOM なしの UTF-8 で保存してください。
正しいフックで呼ぶ
リダイレクトは、ヘッダーを送る前に実行されるフックの中で行います。用途ごとの目安は次のとおりです。
| 用途 | フック | 理由 |
|---|---|---|
| 公開ページ(特定の固定ページ・投稿など) | template_redirect |
表示するページが決まったあと、テンプレートを読み込む前に実行される。is_page() などの条件分岐タグが使える |
| 管理画面 | admin_init |
管理画面の出力が始まる前に実行される |
| ページの種類に関係なく全体 | init |
ただし、is_page() などはまだ使えない(どのページかが決まっていない) |
固定ページ「旧お問い合わせ」(スラッグ old-contact)を、新しいページへ転送する例です。
<?php
add_action( 'template_redirect', function () {
if ( is_page( 'old-contact' ) ) {
wp_safe_redirect( home_url( '/contact/' ), 301 );
exit;
}
} );
このコードは子テーマの functions.php か、自作のプラグインに書きます。
原因3:wp_safe_redirect() で外部URLに移動しない
wp_safe_redirect() は、移動先が自分のサイトと同じホスト(または許可したホスト)のときだけ移動します。それ以外のURLを渡すと、移動先が管理画面(admin_url())に置き換えられます。「外部サイトに移動させたいのに管理画面やログイン画面に飛ぶ」ときは、これが原因です。
ユーザーの入力(URLのパラメーターなど)から移動先を決める場合は、知らない外部サイトへ誘導される「オープンリダイレクト」を防ぐため、wp_safe_redirect() を使います。決まった外部サイトに移動させたいときは、allowed_redirect_hosts フィルターでそのホストを許可します。
<?php
add_filter( 'allowed_redirect_hosts', function ( $hosts ) {
$hosts[] = 'shop.example.com';
return $hosts;
} );
移動先がコードの中で決まっていて、ユーザーの入力を含まない場合は wp_redirect() でもかまいません。
原因4:キャッシュが古いリダイレクトを返している
- ブラウザー:301(恒久的な移動)は、ブラウザーに記録されることがあります。一度301を返すと、コードを直したあとも同じブラウザーでは古い移動先に飛び続けることがあります。動作を確かめている間は302(初期値)を使い、確認できてから301に変えます
- ページキャッシュ・CDN:キャッシュ系のプラグインやCDNが、リダイレクトの応答やリダイレクト前のページを保存していることがあります。コードを変えたら、キャッシュを削除してから確かめます
ブラウザーのキャッシュの影響を受けずに確かめるには、curl で応答のヘッダーを見ます。
curl -sI https://example.com/old-contact/
HTTP/2 301 などのステータスと location: の行が、意図したとおりかを確認します。x-redirect-by: の行があれば、そのリダイレクトはWordPress(またはその値を付けたプラグイン)が出しています。この行が無いリダイレクトは、.htaccess、Webサーバー、CDNなど、WordPressの外で出ている可能性があります。
原因5:リダイレクトがループする
ブラウザーに「リダイレクトが繰り返し行われました」と表示される場合は、移動先でも同じ条件が成り立ち、また同じリダイレクトが実行されています。curl でたどると、どこで繰り返しているかが分かります。
curl -sIL --max-redirs 5 https://example.com/old-contact/ | grep -i -E '^(HTTP|location)'
よくある原因は次の2つです。
- 条件が移動先でも成り立つ:たとえば「ログインしていなければログインページへ」という条件を、ログインページ自身にも適用している。移動先のページを条件から外します
- リバースプロキシやロードバランサーの後ろでHTTPSを判定できていない:CDNやロードバランサーがHTTPSを受けてWordPressにはHTTPで渡す構成では、
is_ssl()がfalseになり、「HTTPSへ移動させる」処理が繰り返されます。プロキシがX-Forwarded-Protoヘッダーを付けている場合は、wp-config.phpの「編集が必要なのはここまでです」より上に次の行を書きます
<?php
if ( isset( $_SERVER['HTTP_X_FORWARDED_PROTO'] ) && 'https' === $_SERVER['HTTP_X_FORWARDED_PROTO'] ) {
$_SERVER['HTTPS'] = 'on';
}
このヘッダーを、プロキシが毎回付け直す(外部から届いた同名のヘッダーを上書きする)構成でのみ使ってください。プロキシを通らずにWordPressへ直接届く経路があると、外部から同じヘッダーを偽って送れます。
自分で書いたHTTPSへのリダイレクトのコードは、.htaccess やサーバーの設定、CDNの設定と重なっていないかも確認します。同じ役割のリダイレクトは1か所にまとめます。
原因6:別のプラグインが移動先を書き換えている
wp_redirect フィルターは、移動先のURLを書き換えたり空にしたりできます。空にされると wp_redirect() は false を返し、何もしません。戻り値を確かめると、この状態かどうかが分かります。
<?php
if ( ! wp_safe_redirect( home_url( '/contact/' ) ) ) {
error_log( 'redirect was cancelled' );
}
exit;
false が返る場合は、プラグインを1つずつ無効にして、どれがフィルターを付けているかを探します。
まとめ
wp_redirect()はヘッダーを送るだけ。呼んだらすぐexit;を書く- 出力が始まったあとでは効かない。公開ページは
template_redirect、管理画面はadmin_initで呼ぶ - ユーザーの入力から移動先を決めるときは
wp_safe_redirect()を使い、外部ホストはallowed_redirect_hostsで許可する - 確認は
curl -sIで行い、テスト中は302を使う。ループはcurl -sILでたどる