WordPressサイトのリニューアルで、旧ドメインから新ドメインへ301リダイレクトを張る機会がありました。
使ったのは定番プラグインの Redirection(検証時 v5.10)です。設定画面はシンプルですが、実際に手を動かすと「この欄は何を選べばいいのか」「正規表現をONにしたら項目が消えた」「検証したら想定外の404が出た」と、細かい判断が次々に出てきました。
さらに、一通り検証を終えて301に切り替えたあとのレビューで、パラメータの扱いに関する問題が3つ見つかりました。そのうち1つは、着地ページは正しいのに途中のURLが壊れていたという、検証しないと気づけない種類のものでした。
この記事では、その過程を順番どおりに残します。
前提
旧サイト : https://old-example.co.jp/ (旧サーバー / WordPress)
新サイト : https://new-example.jp/ (新サーバー / WordPress は /wp/ 配下)
- 旧サイトのWordPressに Redirection を入れて、全URLを新ドメインへ飛ばす
- 旧サイトのURL構造は
/news/{カテゴリ}/{記事ID}/、/works/{カテゴリ}/{スラッグ}/の3階層 - 新サイトは
/news/{ID}/、/works/{ID}/の2階層に整理済み - 旧サーバーのFTP情報は手元にない
最後の一点が、この作業の性格を決めました。
この移行では、サイトの引き継ぎそのものや、お問い合わせメールが届かなくなる問題も起きています。それぞれ前の制作会社からサイトを引き継ぐとき、技術より先に確認すべきアカウントと契約とContact Form 7で自動返信は届くのに管理者宛だけ届かない原因にまとめています。
いきなり全体転送をしない理由
最初に考えたのは「^/(.*)$ を新ドメインへ飛ばす1本で済むのでは」ということでした。
これは危険です。
自分で自分を締め出す
全パスを転送すると、/wp-admin/ も /wp-login.php も新ドメインへ飛びます。旧サイトの管理画面に二度と入れなくなります。
通常なら FTP で wp-content/plugins/redirection をリネームすればプラグインが無効になって復旧できますが、今回はFTP情報がありません。やり直しがきかない状態でした。
301はブラウザに強くキャッシュされる
301(恒久的な移動)はブラウザが覚えてしまうため、設定を間違えてルールを消しても、手元のブラウザでは飛び続けます。「直したのに直らない」が起きます。
FTPなしで張った保険
FTPがなくても、次の3つで事故の芽はかなり潰せます。
1. 管理画面にログインしたブラウザを開いたままにする
検証は別ブラウザかシークレットウィンドウで行い、ログイン済みのタブは閉じない。何かあってもそのタブからルールを無効化・削除できます。
2. 最初は 302 で作る
302(一時的な移動)ならブラウザに強く残らないので、誤りに気づいたらすぐ戻せます。全部の検証が終わってから301に切り替えます。検索エンジンに見せるのは、確認済みの301だけにします。
3. 全体転送のルールでは管理系パスを除外する
^/(?!wp-admin|wp-login|wp-content|wp-includes|wp-json|xmlrpc)(.*)$
否定先読み (?!...) で、管理画面・ログイン・WordPress本体のパスを転送対象から外します。
そのうえで、まずは下層ページ1本で動作確認するところから始めました。
Step 1:正規表現なしの1本で疎通確認
最初のルールは、プラグインが動くことを確かめるためだけのものです。
| 項目 | 値 |
|---|---|
| Source URL | /company/ |
| Regex | OFF |
| Query Parameters | Ignore & pass all parameters to the target |
| Title | 疎通確認用(動作確認後に削除) |
| Match | URL only |
| When matched | Redirect to URL / 302 - Found |
| Target URL | https://new-example.jp/company/ |
| Group | Redirects |
| Position | 1 |
Query Parameters はどれを選ぶか
初期値は Exact match in any order ですが、ドメイン移行ではこれを選ぶと困ります。3つの選択肢の違いはこうです。
| 選択肢 | 挙動 |
|---|---|
| Exact match in any order | Source URL に書いたパラメータと完全一致したときだけマッチ(順不同) |
| Ignore all parameters | パラメータは無視してマッチ。転送先には引き継がない |
| Ignore & pass all parameters to the target | パラメータは無視してマッチし、転送先に引き継ぐ |
Exact match のままだと、クエリの付いたURLがそもそもマッチしません。
/company/ → マッチする
/company/?utm_source=newsletter → マッチしない(旧ドメインで404)
/company/?fbclid=xxxx → マッチしない(旧ドメインで404)
メルマガや広告、SNSでシェアされたリンクには utm_source や fbclid が付いて回ります。それを取りこぼすと、移行の意味が半分なくなります。
Ignore & pass にしておけば、マッチしたうえでパラメータも新URLに引き継がれるので、新サイト側の解析で流入元も追えます。「パラメータは引き継ぐ」——これをこの移行の方針にしました。
Title は必ず入れる
任意項目ですが、ルールが増えると正規表現だけでは何のためのルールか分からなくなります。日本語で目的を書いておくと、半年後の自分が助かります。
確認
シークレットウィンドウで次の3つを開きました。
https://old-example.co.jp/company/ → 飛ぶ
https://old-example.co.jp/company/?utm_source=test → 飛ぶ+パラメータが付いたまま
https://old-example.co.jp/ → まだ飛ばない(正しい)
想定どおりだったので、このルールは削除して次に進みます。
Step 2:正規表現で個別ルールを作る
ここからが本番です。旧サイトの3階層URLを、新サイトの2階層URLへ1対1で対応させます。
Regex をONにすると Query Parameters の欄が消える
歯車アイコンから Regex をONにすると、Query Parameters の選択欄が画面から消えます。
不具合ではありません。Redirection のソースを読むと、理由がそのまま書いてあります。
if ( $this->flag_regex ) {
// Regex auto-disables other things
$this->flag_query = self::QUERY_EXACT;
}
Regex をONにした瞬間、クエリの扱いは「完全一致」に強制的に戻されます。 つまり Step 1 で選んだ Ignore & pass は効かなくなり、クエリをどう扱うかは正規表現の書き方に全部委ねられるということです。プラグインが自動でパラメータを足したり消したりすることはありません。
ここを正しく理解していなかったことが、あとで問題になります。
末尾の $ がクエリ付きURLを弾く
最初はこう書いていました。
^/news/[^/]+/([0-9]+)/?$
Redirection の正規表現は、クエリを含めたURL全体に対して評価されます。末尾を $ で固定しているため、/news/news/56/?utm_source=x のようにクエリが付くとマッチしません。マッチしなかったURLは最後の全体転送ルールに落ち、新ドメインの /news/news/56/(存在しないパス)へ飛んでしまいます。
そこで末尾にクエリを許容するグループを足しました。
^/news/[^/]+/([0-9]+)/?(?:\?.*)?$
(?:...) は非キャプチャグループで、クエリを「読んで捨てる」書き方です。
当時は「クエリを捕まえて転送先に付け直すと、プラグイン側でも自動で付いて二重になるかもしれない」と考えて、あえて捨てる形にしました。実際に検証すると、
/news/news/56/?utm_source=test → https://new-example.jp/news/56/ → 200
着地ページは正しく、パラメータは落ちる。想定どおりだったので、このまま進めました。
この判断の理由付けが間違っていたことは、後半で書きます。
検証で見つかったこと
ルールを登録したら、curl で1本ずつ叩きました。ブラウザより確実で、キャッシュの影響も受けません。
curl -sIL "https://old-example.co.jp/news/news/56/" | grep -Ei "^HTTP|^location"
-I でヘッダーだけ、-L でリダイレクトを最後まで追跡します。何段飛んで、どこに着地したかが全部見えます。
/wp-admin/ が302を返した → 正常
一瞬ヒヤッとしましたが、飛び先を見ると旧ドメインの wp-login.php でした。
location: https://old-example.co.jp/wp-login.php?redirect_to=...&reauth=1
curl はログインCookieを持っていないので、WordPressがログイン画面に案内しているだけです。飛び先が旧ドメインのままであることが確認できればOKです。
ページ送りが404になった
/works/page/2/ → https://new-example.jp/works/page/2/ → 404
最初は works と news のページ送りを1本のルールにまとめていました。
^/(works|news)/page/([0-9]+)/?(?:\?.*)?$ → https://new-example.jp/$1/page/$2/
ところが新サイトの施工実績は全件を1ページに表示する設計にしていたので、/works/page/2/ が存在しません。一方、お知らせは10件ずつのページ送りが残っています。
行き先が違うものを1本にまとめたのが誤りでした。お知らせ用と施工実績用に分割しました。
旧サイトは1ページ3件表示だったので、ページ送りURLは30本近くありました。ここを404にするのはもったいないところでした。
画像のルールが効いていなかった
/wp-content/uploads/2026/07/xxx.jpg → 200(転送されない)
/wp-content/uploads/ 配下の実在するファイルは、Webサーバーが直接返すためPHPが起動せず、Redirection は呼ばれません。 プラグインである以上、これは避けられない制約です。
ただし旧サーバーが生きている限り画像は200で返るので、404にはなりません。ルール自体は「旧サーバーから消えた画像」にだけ効くものとして残しました。
プレースホルダのまま実行していた
手順メモに書いておいたコマンドの {旧カテゴリ}/{旧スラッグ} をそのままコピーして実行し、「404になりました」と悩みかけました。
テスト用のURLは、旧サイトの一覧から実際のリンクをコピーするのが一番確実です。
ページ送りのルールは「門番」
途中で「一覧のページ送りのルール、消してもいいのでは」と思いました。結果から言うと、消すと壊れます。
正規表現を実際に回して確かめました。
ページ送りルールあり
/news/page/2/ → https://new-example.jp/news/page/2/ ✅
/works/page/2/ → https://new-example.jp/works/ ✅
ページ送りルールなし
/news/page/2/ → https://new-example.jp/news/2/ ❌
/works/page/2/ → https://new-example.jp/works/2/ ❌
詳細ページ用のルール ^/news/[^/]+/([0-9]+)/?$ から見ると、/news/page/2/ は「カテゴリ = page、記事ID = 2」に見えてしまいます。
ページ送りのルールは「飛ばすため」だけでなく、詳細ルールに誤解釈させないための門番です。だから詳細ルールより上に置く必要があります。
Position は10刻みで振る
Redirection は Position の小さい順に評価し、最初にマッチしたルールだけを適用します。
Position の数値そのものには意味がなく、相対的な順序だけが効きます。最初は1, 2, 3と振っていましたが、ルールを途中に挟むたびに全部振り直すことになったので、10刻みに変えました。
10, 20, 30, 40, 50, 60, 100
こうしておけば、あとから15や25を差し込めます。全体転送の受け皿は100にして、必ず最後に評価されるようにしました。
なお、一覧画面の初期表示(Standard Display)には Position の列がありません。表示順と評価順は一致しないので、表示の切り替えで「Display All」を選び、Pos 列を出して確認するのが確実です。Pos 列で並べ替えれば、評価される順番どおりに並びます。Position が同じ値のルールは、先に作ったものが先に評価されます。
残骸ルールに注意
ページ送りを分割したとき、最初の合体版ルールが残っていて、8本になっていました。合体版が先に評価されると /works/page/2/ がまた404に飛びます。
分割したら、元のルールは必ず削除する。 当たり前ですが、編集フォームを開いたまま別のルールを追加していると見落とします。
2ホップのリダイレクト
施工実績の詳細ページだけは、リダイレクトが2段になる設計にしました。
https://old-example.co.jp/works/category/{旧スラッグ}/
301 → https://new-example.jp/works/{旧スラッグ}/ ← 旧サーバーの Redirection
301 → https://new-example.jp/works/130/ ← 新サイトの WordPress
200
新サイトでは、日本語スラッグが長すぎたため投稿スラッグを投稿IDに揃えました。このとき wp_update_post() を通すと、WordPressが旧スラッグを _wp_old_slug というメタデータに自動保存します。新サイトに旧スラッグでアクセスがあると、wp_old_slug_redirect() が新しいURLへ301してくれます。
おかげで、旧スラッグと新IDの対応表(28行)を Redirection に手で登録する必要がなくなりました。Google の公式ドキュメントによると、Googlebot はリダイレクトの連鎖を最大10ホップまでたどり、推奨は3ホップ以下です。2段なら問題ありません。
302から301へ切り替え
全ルートの検証が終わったら、7本すべてを編集して HTTP code を 301 - Moved Permanently に変更します。
切り替え後にもう一度 curl を流し、1段目がすべて 301 になっていること、wp-login.php が 200 のままであることを確認しました。
ここで一度「完了」としました。
公開後のレビューで見つかった3つの問題
設定の記録をまとめてレビューしてもらったところ、次の指摘を受けました。
クエリの引き継ぎが、個別ルールと受け皿ルールで逆になっています。
改めてクエリ付きのURLで一通り叩き直すと、問題は3つありました。
問題1:パラメータを「引き継ぐルール」と「落とすルール」が混在していた
/news/news/56/?utm_source=test → /news/56/ ← 個別ルール:落ちる
/company/?utm_source=test → /company/?utm_source=test ← 受け皿ルール:残る
先ほど書いたとおり、Regex ON ではクエリの扱いが正規表現に委ねられます。
個別ルール (?:\?.*)?$ クエリを「読んで捨てる」 → 転送先から消える
受け皿ルール (.*)$ クエリごと $1 に入れる → 転送先に残る
「パラメータは引き継ぐ」という Step 1 の方針に対して、URLによって引き継いだり落としたりしていたわけです。
そして「捕まえて付け直すと二重になるかもしれない」という心配は、ソースを読めば起きないことが分かります。 Regex ON ではクエリ設定が完全一致に強制され、プラグインが自動でパラメータを付け足すことはないからです。
さらに言えば、自分の検証結果がすでにそれを示していました。 ?utm_source=test を付けて叩いて、パラメータが落ちた。もしプラグインが自動で付け足しているなら、落ちるはずがありません。データは手元にあったのに、「着地ページが正しい」ことに安心して、そこを読み落としていました。
問題2:[^/]+ がクエリを飲み込んで、壊れたURLを作っていた
指摘を受けて末尾スラッシュなしのパターンも試したところ、もっとまずいものが出てきました。
curl -sIL "https://old-example.co.jp/works/category/{スラッグ}?utm_source=test" | grep -Ei "^HTTP|^location"
HTTP/2 301
location: https://new-example.jp/works/{スラッグ}?utm_source=test/ ← ?
HTTP/2 301
location: https://new-example.jp/works/130/
HTTP/2 200
curl -sIL "https://old-example.co.jp/works/category/?utm_source=test" | grep -Ei "^HTTP|^location"
HTTP/2 301
location: https://new-example.jp/works/?utm_source=test/ ← ?
HTTP/2 200
[^/]+ は「スラッシュ以外すべて」なので、? から後ろも取り込みます。
/works/category/{スラッグ}?utm_source=test
└──── $1 ────────────────┘ ← クエリごとスラッグ扱い
→ /works/{スラッグ}?utm_source=test/ ← 末尾に / が付いて、値が「test/」になる
/works/category/?utm_source=test
└─ $1 ──────────┘ ← クエリをスラッグと誤認
→ カテゴリー用のルールより先に、詳細用のルールが拾ってしまう
厄介なのは、どちらも最終的な着地ページは正しいことです。新サイトのWordPressが壊れたURLを寛容に解釈して、それぞれ /works/130/ と /works/ に落ち着いています。ブラウザで開いても何も問題なく見えます。
ただしGoogleアナリティクスには utm_source が「test」ではなく「test/」として記録されるので、同じ流入元が別物として分かれてしまいます。着地が正しいぶん、検証しないと絶対に気づかない種類の不具合でした。
対処は [^/]+ を [^/?]+ に変えるだけです。スラッシュに加えて ? も区切りとして扱います。
問題3:新サイト側の2段目でもパラメータが落ちていた
問題2の1本目の出力をよく見ると、もうひとつ分かることがあります。
location: https://new-example.jp/works/{スラッグ}?utm_source=test/ ← 新サイトはクエリを受け取っている
location: https://new-example.jp/works/130/ ← 転送先では消えている
新サイトの WordPress は、旧スラッグから新URLへ301するときにクエリを落としていました。wp_old_slug_redirect() は転送先を get_permalink() で組み立てるので、元のリクエストに付いていたクエリは引き継がれません。
つまり1段目の Redirection を直しても、施工実績だけは最後までパラメータが通らないということです。通すには新サイトのテーマに old_slug_redirect_url フィルタを足して、クエリを付け直す必要があります。
直したもの:問題2だけ
3つのうち、壊れたURLを作っていた問題2だけを直しました。
施工実績の2本のルールで、[^/]+ を [^/?]+ に変えただけです。Target URL は変えていません。
旧施工実績詳細
変更前 ^/works/[^/]+/([^/]+)/?(?:\?.*)?$
変更後 ^/works/[^/?]+/([^/?]+)/?(?:\?.*)?$
旧施工実績カテゴリー
変更前 ^/works/[^/]+/?(?:\?.*)?$
変更後 ^/works/[^/?]+/?(?:\?.*)?$
修正後の結果です。
/works/category/{スラッグ}?utm_source=test
変更前 → /works/{スラッグ}?utm_source=test/ ❌ 壊れたURL
変更後 → /works/{スラッグ}/ → /works/130/ ✅
/works/category/?utm_source=test
変更前 → /works/?utm_source=test/ ❌ 詳細用ルールに誤マッチ
変更後 → /works/ ✅ カテゴリー用ルールが拾う
同じルールを書き換えたので、末尾スラッシュありの普通のURLとページ送りも叩き直し、影響がないことを確認しました。
直さなかったもの:問題1と問題3
パラメータを全ルールで引き継ぐ形にすることもできました。個別ルールの (?:\?.*) を (\?.*) に変えてクエリを捕まえ、Target の末尾に $2 を付ける。あわせて新サイト側にフィルタを足せば、施工実績も最後まで通ります。
それでも今回は、パラメータは落とすままにすると判断しました。理由は2つです。
- 影響するのは「旧ドメインのURLに
utm_source等を付けて来た人」の流入元が追えないことだけで、着地ページは全部正しい - 旧ドメインは運用期限が決まっており、そこまでの期間のためにルール5本とテーマを触るほどの価値はない
結果として、個別ルールは落とし、受け皿ルールは引き継ぐという不整合は残っています。知ったうえで許容した、という扱いです。
もし引き継ぐ形にするなら、キャプチャの番号に注意が必要です。ルールによって (...) の数が違うので、クエリが $2 になるルールと $1 になるルールがあります。私も一度、施工実績のページ送りを $1 と書き間違えかけて、正規表現をシミュレーションで回して気づきました。
完成形のルール
共通設定:Regex ON / Match URL only / Redirect to URL / 301 / Group Redirects
| Position | Title | Source URL | Target URL |
|---|---|---|---|
| 10 | お知らせのページ送り | ^/news/page/([0-9]+)/?(?:\?.*)?$ |
https://new-example.jp/news/page/$1/ |
| 20 | 施工実績のページ送り → 一覧へ集約 | ^/works/page/([0-9]+)/?(?:\?.*)?$ |
https://new-example.jp/works/ |
| 30 | 旧お知らせ詳細 → /news/{ID}/ | ^/news/[^/]+/([0-9]+)/?(?:\?.*)?$ |
https://new-example.jp/news/$1/ |
| 40 | 旧施工実績詳細 → 新サイトでIDへ再301 | ^/works/[^/?]+/([^/?]+)/?(?:\?.*)?$ |
https://new-example.jp/works/$1/ |
| 50 | 旧施工実績カテゴリー → 一覧へ集約 | ^/works/[^/?]+/?(?:\?.*)?$ |
https://new-example.jp/works/ |
| 60 | 旧アップロード画像 → /wp/ 配下 | ^/wp-content/uploads/(.*)$ |
https://new-example.jp/wp/wp-content/uploads/$1 |
| 100 | 旧ドメイン全体 → 新ドメイン(受け皿) | ^/(?!wp-admin|wp-login|wp-content|wp-includes|wp-json|xmlrpc)(.*)$ |
https://new-example.jp/$1 |
※Position 10〜50 はクエリを捨て、100 はクエリを引き継ぎます(上記「直さなかったもの」を参照)。
※Position 30 の [^/]+ は、後ろが数字の ([0-9]+) なので ? を飲み込む問題が起きず、そのままにしています。
お知らせのカテゴリー一覧にルールがない理由
施工実績には「カテゴリー → 一覧へ集約」(Position 50)がありますが、お知らせにはありません。これは意図的です。
旧サイトには、お知らせのカテゴリー一覧が /news/{カテゴリ}/ の形で3つありました。新サイトではお知らせのカテゴリーは廃止せず、URLも旧サイトと同じ /news/{カテゴリ}/ のまま維持しています。そのため個別のルールは不要で、受け皿(Position 100)が同じパスへそのまま転送すれば正しく着地します。
/news/news/ → https://new-example.jp/news/news/ → 200
/news/blog/ → https://new-example.jp/news/blog/ → 200
/news/recruit/ → https://new-example.jp/news/recruit/ → 200
一方、施工実績は新サイトでカテゴリー自体を廃止したので、行き先がなくなった旧カテゴリー一覧を一覧ページへ集約するルールが必要でした。移行先にURLが残っているかどうかで、ルールが要るかどうかが決まります。
なお、カテゴリー一覧のページ送り(/news/news/page/2/ など)も受け皿でそのまま転送されます。旧サイトは1ページ3件、新サイトは10件表示なので、旧サイトの後ろのほうのページ番号は新サイトには存在せず404になります。ここは許容しました。
検証コマンド集
クエリ付きのURLは、zsh では ? と & が特殊文字なので必ずダブルクォートで囲みます。
# 締め出されていないか(最重要)
curl -sI "https://old-example.co.jp/wp-login.php" | head -1
curl -sI "https://old-example.co.jp/wp-admin/" | grep -i "^location"
# 各パターン(クエリなし)
curl -sIL "https://old-example.co.jp/news/news/56/" | grep -Ei "^HTTP|^location"
curl -sIL "https://old-example.co.jp/news/page/2/" | grep -Ei "^HTTP|^location"
curl -sIL "https://old-example.co.jp/works/page/2/" | grep -Ei "^HTTP|^location"
curl -sIL "<旧サイトからコピーした施工実績の実URL>" | grep -Ei "^HTTP|^location"
curl -sIL "https://old-example.co.jp/company/" | grep -Ei "^HTTP|^location"
curl -sIL "https://old-example.co.jp/" | grep -Ei "^HTTP|^location"
# クエリ付き(着地が正しいか・パラメータがどう扱われるか)
curl -sIL "https://old-example.co.jp/news/news/56/?utm_source=test&utm_medium=blog" | grep -Ei "^HTTP|^location"
curl -sIL "https://old-example.co.jp/company/?utm_source=test" | grep -Ei "^HTTP|^location"
# 末尾スラッシュなし+クエリ(壊れたURLにならないか)
curl -sIL "<施工実績の実URLから末尾の / を取ったもの>?utm_source=test" | grep -Ei "^HTTP|^location"
curl -sIL "https://old-example.co.jp/works/category/?utm_source=test" | grep -Ei "^HTTP|^location"
見るのは着地ページだけではありません。 途中の location: にパラメータが正しく付いているか、値に余計な / が混ざっていないかまで確認します。問題2は、着地だけ見ていたら永遠に気づけませんでした。
ブラウザで確認する場合は、毎回新しいシークレットウィンドウを開いてください。 同じウィンドウを使い回すとリダイレクトがキャッシュされ、ルールを直しても古い挙動が見え続けます。
まとめ
作業を通して残ったチェックリストです。
- 管理画面にログインしたブラウザを開いたまま作業する
- 最初は正規表現なしの1本で疎通確認する
- Query Parameters は
Ignore & pass all parameters to the target - Title にルールの目的を日本語で書く
- Regex ON ではクエリの扱いが完全一致に強制される。 引き継ぐか捨てるかは正規表現で決まる。引き継ぐなら
(\?.*)?$で捕まえて Target に$N、捨てるなら(?:\?.*)?$ - 引き継ぐ/捨てるをルール全体で揃えるか、揃えないなら理由を持っておく
- 区切りの文字クラスは
[^/]+ではなく[^/?]+ - 全体転送のルールでは管理系パスを否定先読みで除外する
- ページ送りのルールは詳細ルールより上に置く
- Position は10刻み。受け皿は100
- 分割・変更したら元のルールを消す
- 最初は302、検証が終わったら301
- 検証は
curl -sILで。着地ページだけでなく途中のlocation:も見る - クエリ付き・末尾スラッシュなしのURLでも試す
- 多段リダイレクトなら、2段目以降でパラメータがどう扱われるかも確認する(WordPress の旧スラッグ転送は落とす)
- 静的ファイルには Redirection が効かないことを知っておく
振り返ると、一番の教訓は「想定どおりの結果が出たときこそ、結果を全部読む」でした。パラメータが落ちたのを見て「想定どおり」で済ませたとき、その結果は自分の前提(プラグインが自動で付け足すかもしれない)が間違っていることを示していました。
もうひとつは、見つかった問題を全部直す必要はないということです。壊れたURLを作っていた問題2はすぐ直しましたが、パラメータの不整合は、影響と手間を比べて残す判断をしました。大事なのは、知らずに残っているのではなく、知ったうえで残していることだと思います。
後日談:WordPressが落ちるとリダイレクトも止まる
設定からしばらくして、旧サーバーのWordPressがコアファイルの更新失敗で停止し、トップページも管理画面もHTTP 500を返すようになりました。
このとき、Redirection で設定した301もすべて止まりました。 Redirection はWordPressのプラグインなので、WordPressが動かなければリダイレクトも動きません。当たり前のことですが、落ちて初めて実感しました。
しかも管理画面まで500なので、Redirection の設定を見ることすらできません。FTP情報も相変わらずない。こちらからは何ひとつ手を出せない状態でした。
できたのは、外から状況を切り分けて、サーバーを管理している前の管理会社に伝えることだけです。
静的ファイル(画像・JS) → 200 Webサーバーは正常
存在しないURL(画像を含む) → 500 本来は404。PHPに渡るものが全部死んでいる
レスポンス本文 → 0バイト
WordPress には、プラグインが致命的エラーを起こしたときに「このサイトで重大なエラーが発生しました」という画面を出す仕組みがあります。それすら出ずに本文が0バイトということは、プラグインではなく、もっと手前で止まっている。そう伝えたところ、前の管理会社がエラーログから原因を特定してくれました。
PHP Fatal error: Cannot redeclare addslashes_gpc()
WordPress 本体の自動更新が途中で失敗し、新旧のコアファイルが混在していたことが原因でした。前の管理会社がコアファイルを入れ替えて復旧し、データベースはそのままだったので、Redirection のルールも元どおりに動き出しました。こちらは最後まで FTP に触れていません。
旧サイトの役割がリダイレクトだけになったのであれば、WordPress を置かずに .htaccess のリライトルールだけで301を書くほうが堅牢です。PHPを実行しないので、PHPやWordPressの不具合で止まることがありません。ちなみに .htaccess の RewriteRule はパス部分だけを見て、クエリは転送先に自動で引き継ぐので、今回の問題1と問題2はそもそも起きません。
ただし、FTPがない立場では .htaccess は自分で書けません。その場合は、ルール一式を用意して、サーバーを管理している側に設置を依頼するという形になります。私も復旧の過程で前の管理会社にこの構成を提案しました。
FTPなしで Redirection を使うなら、もうひとつ押さえておくことがあります。サーバーを管理している人と、すぐ連絡がつく状態にしておくこと。 WordPress が止まったとき、FTPも管理画面も使えない側にできるのは、状況を正確に伝えることだけだからです。
Redirection は「まず安全に試して検証する」段階では非常に便利ですが、WordPress に依存する以上、長期運用ではサーバーレベルのリダイレクトへの移行も検討する価値がある、というのが最後の学びでした。
