ドメイン移行や HTTPS 化の後、「プラグインの設定が全部消えた」「Elementor で作ったレイアウトが崩れた」という事故が起きることがある。原因の大半は、データベースの文字列置換をシリアライズに対応していない方法で実施したことにある。
WordPress のデータベースには、PHP のシリアライズ形式で保存されている値が大量に混在している。phpMyAdmin の「検索と置換」や sed による .sql ファイル直接編集は、このシリアライズを知らずに文字列だけを変えるため、データ構造が破損する。wp search-replace はこの問題を回避するために設計されているが、なぜ安全なのかを理解しておくと、実行前後の判断が確実になる。
PHP シリアライズが「長さを記録している」という問題
WordPress の wp_options テーブル(プラグイン設定)や wp_postmeta テーブル(カスタムフィールド)には、配列やオブジェクトを文字列として保存したシリアライズ値が格納されている。
シリアライズされた文字列はこのような形をしている:
a:2:{s:4:"home";s:22:"http://example.com/top";s:5:"title";s:8:"サイト名";}
このうち s:22:"http://example.com/top" は「22 バイトの文字列」を意味している。s:N: の N がバイト数を示す。
ここで http://example.com を https://example.com に置換すると何が起きるか。
- 置換前:
s:22:"http://example.com/top"(22 バイト) - 置換後:
s:22:"https://example.com/top"(23 バイト)
s:22 の部分は置換されないまま残るが、実際の文字列は 23 バイトになっている。この不整合を unserialize() が検出して、値の取得が false を返すようになる。プラグインは設定値を読み出せなくなり、「設定が全部消えた」ように見える。
phpMyAdmin の「検索と置換」は SQL レベルの単純な文字列置換なので、s:N: の N を更新しない。同じ理由で、mysql コマンドの UPDATE 文や sed による .sql ファイルの直接編集も同様に壊れる。
wp search-replace が安全な理由
wp search-replace は内部でシリアライズを認識して処理する。
- カラムの値を取り出す
- シリアライズされているかどうかを
is_serialized()で判定する - シリアライズされている場合は一度
unserialize()して展開する - 展開された配列・オブジェクトの各値に対して文字列置換を実行する
serialize()で再度シリアライズして書き戻す
この順番を踏むことで、置換後の値に対して正確なバイト数が計算し直され、s:N: の N が常に正しい値になる。phpMyAdmin が「SQL レベルで値を直接書き換える」のに対して、wp search-replace は「PHP レベルで値を理解してから書き換える」という違いがある。
基本的な使い方
# 実行前にバックアップを取る(必須)
wp db export before-migration-$(date +%Y%m%d).sql
# ドライラン(--dry-run)で変更件数だけ確認する
wp search-replace 'http://example.com' 'https://example.com' --dry-run
# 問題がなければ実行
wp search-replace 'http://example.com' 'https://example.com'
--dry-run は変更を加えずに「何件置換されるか」だけを表示する。実行前に必ず確認する。
テーブルを絞って実行する
デフォルトでは全テーブルを対象にするが、対象テーブルを指定して絞ることもできる。
# wp_posts と wp_postmeta のみ(投稿・カスタムフィールド)
wp search-replace 'http://example.com' 'https://example.com' wp_posts wp_postmeta
# wp_options のみ(プラグイン設定・サイト URL 等)
wp search-replace 'http://example.com' 'https://example.com' wp_options
wp_posts が大きなサイトでは、テーブルを分けて実行すると途中状態が把握しやすくなる。
--skip-columns=guid を付けた方がよいケース
WordPress の wp_posts テーブルには guid という列がある。投稿のユニーク識別子で、RSS フィードの投稿 ID として使われる。guid を変更すると、購読者の RSS リーダーが同じ投稿を「新着」として重複取得する可能性がある。
wp search-replace 'http://example.com' 'https://example.com' --skip-columns=guid
RSS を配信していない新規構築サイトでは省略できるが、既存サイトの HTTPS 化では付けておく方が安全。
実行後の確認
# サイト URL が正しく更新されているか
wp option get home
wp option get siteurl
# 置換漏れがないか(0 件になれば完了)
wp search-replace 'http://example.com' 'https://example.com' --dry-run
最後の --dry-run で 0 件になれば置換漏れがない。
まとめ
| 方法 | シリアライズ対応 | 実行ログ |
|---|---|---|
| phpMyAdmin「検索と置換」 | ❌ 破損する | 残らない |
SQL UPDATE 直書き |
❌ 破損する | 残らない |
wp search-replace |
✅ 安全 | SSH ログに残る |
phpMyAdmin による置換後に設定消失が起きた場合、実行前に取得したバックアップを wp db import で戻した上で wp search-replace を使い直すことができる。破損したシリアライズデータを後から修復する方法はないため、実行前バックアップと --dry-run の 2 点は省略しない。
WP-CLI が動かない状態への対処(--skip-plugins 起動)やプラグイン単品ロールバック、管理画面ロックアウト時の緊急復旧と合わせて読むと、移行作業前後の対処方針が整理できる。