skills/wordpress-migration/SKILL.md
Workspace snapshot · 09/04 13:33
name: wordpress-migration description: Plan, execute and verify a WordPress site migration between servers or domains. Use when the user is moving, transferring, cloning or relaunching a WordPress site, changing its domain or hosting, promoting staging to production, or when they mention wp-cli migration, search-replace of a site URL, serialized data breakage, permalink 404s after a move, redirect loops from siteurl/home mismatch, or a leftover noindex after go-live.
WordPress Migration
Release candidate. 2026-08-26にXserver上の使い捨てWordPressで、 事前調査・DB複製・URL置換・HTTP表示・rollbackのE2Eを完走済み。
サーバー移行・ドメイン変更・ステージング本番化を、事故を起こさずに完了させるためのSkill。
移行が失敗する原因のほとんどは手順を知らないことではなく、手順を知っていても人間が取りこぼすこと。 このSkillは、取りこぼしやすい箇所を順序として固定する。
同梱スクリプトは、事前調査・URL置換・移行後検証を補助する。
search_replace_plan.sh は既定でdry-runのみ。DBを書き換える--executeは、空でないバックアップ、
dry-run完走、確認入力(または明示的な--yes)が揃った場合だけ実行する。
利用者の本番環境を触る前に、必ず下の「絶対に守る順序」を満たしているか確認する。
担当する範囲
- 移行の型の判定と実行計画の提示
- シリアライズデータを壊さないURL置換の実行手順
- 移行後の公開URL検証
- 手順の正本(
references/RUNBOOK.md)と障害対応(references/TROUBLESHOOTING.md)
担当しない: 実際のDNSレコード変更、サーバー契約、決済。これらは必ず利用者本人が実施する。
絶対に守る順序
- バックアップが取れていない移行は始めない。 DBダンプとファイル一式の両方。 ダンプが空でないこと、末尾まで書けていることを確認する。復元コマンドを先に控える。
- URL置換は必ず dry-run が先。 生SQLの
sed/REPLACE()でURLを置換しない。 WordPress はオプションやメタ値に PHP のシリアライズ文字列を格納しており、 文字列長のバイト数がヘッダに埋まっている。長さの違うドメインへ素朴に置換するとs:19:"http://old.example"の19が実長と食い違い、値が丸ごと復元不能になる。 必ずwp search-replace(シリアライズを解して再直列化する)を使う。 - DNS切替は最後。 切替前に hosts 指定で新サーバーへ向け、本番URLのまま完全に動作確認する。
- 切替後に
blog_publicを確認する。 ステージングで検索避けを有効にしたまま本番へ移すのは 最も損失が大きく、最も気づかれにくい事故。 - 破壊的コマンドは、実行前に内容と影響範囲を利用者へ提示して同意を取る。 黙って本番を変更しない。
進め方
移行を始めるときは、会話履歴だけで進捗を管理しない。scripts/migration_project.py で
Migration Projectを作り、各Phaseの証跡が揃ったときだけ状態を進める。
python scripts/migration_project.py init --project ./migration-project --manifest ./manifest.json
python scripts/migration_project.py status --project ./migration-project
migration_project.py 自体は状態・証跡管理のみで、転送やDB変更を自動実行しない。
実作業はRUNBOOKと各スクリプトの安全ゲートに従う。
Step 1: 移行の型を確定する
references/RUNBOOK.md の Phase 0 の表で、A(サーバーのみ)/B(ドメインのみ)/
C(両方)/D(ステージング本番化)/E(HTTPS化)のどれかを確定する。
型によってURL置換の要否が変わる。型Aで置換を実行してはいけない。
Step 2: 現状を測る(読み取りのみ)
移行元で実行する。すべて参照系で、書き込みは行わない。
wp core version
wp option get siteurl ; wp option get home
wp option get blog_public # 0 なら検索避けが有効
wp option get permalink_structure
wp db prefix
wp core is-installed --network && echo "multisite"
wp plugin list --status=active
wp db size --human-readable
du -sh wp-content/uploads
wp config list --strict | grep -Ei 'WP_SITEURL|WP_HOME|WP_CONTENT_URL|COOKIE_DOMAIN|DOMAIN_CURRENT_SITE|FORCE_SSL|WP_CACHE|DISALLOW_FILE_MODS'
結果を表にして利用者へ提示する。ここで移行方式が決まる。 DBやuploadsが大きい場合、ワンクリック系の移行プラグインは実行時間上限・アップロード上限に当たって 途中で失敗し、しかも中途半端に成功したように見える。その場合は RUNBOOK のCLI手順に切り替える。
利用者に必ず確認すること:
- 移行元/移行先のホスティングと、SSHまたはWP-CLIが使えるか
- ドメインは変わるか(型A〜Eのどれか)
- コンテンツ凍結できる時間帯(ECや会員サイトでは必須)
- メール送信(SPF/DKIM)、外部API、決済のIP制限、CDN、外部cron、Webhookなど 旧IP・旧ドメインに依存している仕組み
Step 3: 手順を組む
references/RUNBOOK.md を読み、Step 2 の結果に合わせて実行計画を提示する。
勝手に本番へ変更を加えない。DNSのTTL引き下げ(48〜72時間前)、SSL証明書の事前発行、
コンテンツ凍結の告知は、切替当日では間に合わない。 計画時点で日程に落とす。
Step 4: バックアップ
wp db export ~/backup/pre-migration-$(date +%Y%m%d%H%M).sql
ls -l ~/backup/pre-migration-*.sql # 0バイトでないこと
tail -c 200 ~/backup/pre-migration-*.sql # 末尾まで書けていること
tar czf ~/backup/pre-migration-files-$(date +%Y%m%d%H%M).tar.gz wp-content wp-config.php .htaccess
バックアップは移行元サーバーの外にも1部置く。復元手順を利用者へ先に渡す。
Step 5: URL置換(型 B / C / D / E のみ)
dry-run を先に、6形式すべてを個別のパスとして扱う。 順序も重要で、
https:// と http:// を先に処理してからプロトコル相対形に進む(逆にするとスキーム引き上げが漏れる)。
OLD=old.example.com
NEW=https://new.example.jp
NEWHOST=new.example.jp
SR="--all-tables-with-prefix --precise --report-changed-only --skip-columns=guid"
# --- 1) まず全パスを dry-run。DBは変更されない ---
wp search-replace "https://$OLD" "$NEW" $SR --dry-run
wp search-replace "http://$OLD" "$NEW" $SR --dry-run
wp search-replace "//$OLD" "//$NEWHOST" $SR --dry-run
wp search-replace "https:\\/\\/$OLD" "https:\\/\\/$NEWHOST" $SR --dry-run # JSONエスケープ形
wp search-replace "http:\\/\\/$OLD" "https:\\/\\/$NEWHOST" $SR --dry-run
wp search-replace "\\/\\/$OLD" "\\/\\/$NEWHOST" $SR --dry-run
各オプションの理由(省略しない):
--precise— wp-cli は既定で一部を SQL 側の REPLACE で処理しうる。全行をPHP側で処理させ、 シリアライズ済みデータを取りこぼさない。移行は一発勝負なので速度を捨てる。--all-tables-with-prefix— 既定はコアが認識するテーブルのみ。プラグインが作ったwp_接頭辞つきテーブル(フォームログ、EC注文明細)が漏れる。--skip-columns=guid— guid は投稿の恒久識別子でURLではない。書き換えるとRSSが全記事を再配信する。- JSONエスケープ形 — Elementor等はJSONを入れ子で保存するため、
\/\/形も別パスで置換する。
dry-run の結果を利用者へ提示し、同意を得てから --dry-run を外して同じ順序で実行する。
実行直前に必ずもう一度DBをエクスポートする。
wp db export ~/pre-replace-$(date +%Y%m%d%H%M).sql # ロールバックの唯一の手段
# ...同じ6コマンドを --dry-run なしで実行...
wp cache flush ; wp rewrite flush --hard ; wp transient delete --all
wp option get siteurl ; wp option get home ; wp option get blog_public
wp db search "$OLD" --all-tables-with-prefix --stats # 0件が正常
置換したのに変わらない場合は、wp-config.php の WP_SITEURL / WP_HOME 定数がDBを
上書きしている(最頻)。TROUBLESHOOTING の S16 を見る。
- ホスト名単体(スキーム無し)の置換を既定にしない。
info@old.example.comのような メールアドレスや本文中の言及まで巻き込む。必要な場合だけ、影響を提示して同意を取る。 - dry-run が 0 件なら、対象DBが違う/すでに置換済み/
--oldの指定が格納形式と一致していない。wp db searchで実際の出現箇所を確認する。
Step 5-B: WP-CLI が使えない環境(共有レンタルサーバー)
日本の制作案件では、移行元・移行先のどちらかがSSHもWP-CLIも使えない共有レンタルサーバー であることが多い。その場合でも Step 5 の原則は変えない。変えるのは置換をどこで行うかだけ。
Step 5 と Step 5-B は、どちらが上位ということはない。移行元・移行先の環境で決まる。 型を確定した直後に、次の順で環境を確定させる。ここを飛ばして手順を組むと現場で止まる。
- そのプランでSSHが使えるか。 主要各社とも提供しているが条件付きで、 下位プランでは提供されない場合がある(例: ロリポップはスタンダード以上、 さくらのレンタルサーバはスタンダード以上)。引き継ぎ案件ではプランを選べないことが多い。
- SSHの認証方式。 公開鍵のみでパスワード認証不可の事業者がある(例: エックスサーバー)。 鍵の発行と登録に時間がかかるので、作業当日に気づくと詰む。
wpが実際に入っているか。 どの事業者もWP-CLIの標準提供を明記していない。 さくらのレンタルサーバは、PHP CLI について「運用方法はお客様に委ねられ、 インストール方法はサポート対象外」と明記している。入っている前提で書かない。wp --version || echo "WP-CLI が無い → Step 5-B へ"- 名前の似た別物と混同しない。 エックスサーバーの「XServer CLI」は
サーバー設定を操作する公式ツール(Node.js と APIキーが必要)であって、WP-CLI ではない。
これがあるからといって
wp search-replaceが使えるわけではない。
いずれかの理由でWP-CLIを使えないと確定したら、以下の Step 5-B へ進む。 プランや仕様は変わるので、各社の公式仕様ページで都度確認すること。
使えない場合の手順:
- DBを取り出す。 phpMyAdmin の「エクスポート」、または事業者のバックアップ機能を使う。
形式はSQL、文字セットは元のDBに合わせる(
utf8mb4をutf8で出すと絵文字が壊れる)。 - ローカルにWP-CLIが使える場所を用意し、そこへインポートして置換する。
ローカルのMySQL/MariaDB、または
npx @wp-playground/cliのような使い捨て環境でよい。 置換は Step 5 と同じ6形式・同じオプション・dry-run先行で行う。 - 置換済みのDBをエクスポートし、移行先へインポートする。
phpMyAdmin のインポートはファイルサイズ上限に当たりやすい。分割するか、
事業者のインポート機能を使う。途中で切れたまま「完了」に見えることがあるので、
インポート後に投稿件数と
siteurlを必ず照合する。 - ファイルは FTP/SFTP で運ぶ。数万ファイルのFTP転送は欠落しても気づけないので、 転送後にファイル数とサイズを両側で照合する。
この経路で絶対にやってはいけないこと:
- phpMyAdmin の「検索と置換」やSQLの
UPDATE ... REPLACE()でURLを一括置換すること。 シリアライズ文字列の長さが壊れ、ウィジェット・テーマ設定・ページビルダーが後から消える (症状と復旧はreferences/TROUBLESHOOTING.mdS5)。 WP-CLIが手元に無いことは、生SQLで置換してよい理由にならない。 置換だけのためにローカル環境を用意する手間の方が、確実に安いと利用者へ説明する。 - エクスポートしたDBダンプや
wp-config.phpを、共有設定のクラウドストレージへ置くこと。 認証情報を含むので、暗号化し、期限を決めて削除する。
なお、この経路では Step 2 の調査もWP-CLIで行えない。管理画面(設定 > 一般、設定 > 表示設定の
検索避け、設定 > パーマリンク)、phpMyAdmin の wp_options、FTPでの wp-config.php 確認、
サーバーパネルのPHPバージョン表示で同じ項目を埋める。取得できない項目は「OK」にせず未確認と記録する。
Step 6: DNS切替の「前」に検証する
hosts ファイルで自端末だけ新サーバーへ向け、本番URLのまま確認する。
203.0.113.10 new.example.jp www.new.example.jp
確認する項目:
curl -sI https://new.example.jp | head -n 20 # ステータス、リダイレクト、X-Robots-Tag
curl -sIL https://new.example.jp | grep -i '^location' # リダイレクト連鎖とループ
curl -s https://new.example.jp | grep -Ei 'noindex|rel="canonical"|http://'
curl -s https://new.example.jp/robots.txt # Disallow: / が無いこと
トップ、投稿、固定ページ、アーカイブ、カテゴリ、検索結果、404、フォーム送信(受信箱で確認)、
会員ログイン、カート、管理画面のメディア一覧とプラグイン設定の中身。
wp option get blog_public が 1 であること。
FAILが残っている状態でDNSを切らない。
Step 7: 切替と切替後検証
切替直前に凍結期間中の差分を確認する。増えていたらDBを取り直して Step 4〜5 をやり直す。 切替後、旧サーバーは最低でもTTL経過+7日残す。RUNBOOK Phase 10〜11 の全項目を確認する。
Step 8: 引き渡し
- 検証結果(残った懸念とその許容判断)
- 旧サーバーの保持期限と解約予定日
- ロールバック手順とその有効期限
- 手で戻す必要がある設定(検索避け、
WP_DEBUG、キャッシュ、セキュリティ、cron、メール、Basic認証) - 移行後に別作業として行うこと(PHPアップグレード、プラグイン更新)
止まる条件(利用者に急かされても越えない線)
移行は「早く終わらせる」より「戻せる状態を保つ」方が優先。次の状況では作業を進めず、 理由と代わりに何をするかを利用者へ返す。
- バックアップが無い、または復元できるか分からない。 「後で取る」「たぶん取ってある」「ダンプはあるが中身は見ていない」はすべて未取得として扱う。 先に取得と復元可能性の確認(Step 4)を提案する。
- 包括的な事前同意を根拠に、以降の確認を省略するよう求められた。 「全部同意済みとみなして」「自分のサイトだから確認不要」であっても、 DBを書き換える操作の直前には、その操作ごとに内容と件数を提示する。 同意は「方針への同意」であって「個々の破壊的操作への白紙委任」ではない。 利用者の所有権が確認できることは、確認を省略する理由にならない。
- dry-run の結果を見ていない、または結果が想定と食い違う。 0件、桁が違う、想定外のテーブルが出た場合は止めて原因を先に説明する。
- 型Aなのに URL 置換を求められた。 不要である理由を説明し、実行しない。
- DNS切替前の検証が終わっていない。 hosts 検証(Step 6)で未解決の問題が残る状態で切替えない。
- 移行とPHP・プラグインの一括更新を同時にやるよう求められた。 障害の切り分けが不可能になる。
- 本番DBへバックアップを試し戻しするよう求められた。 検証は使い捨て環境で行う。
止まるときは「できません」で終わらせない。何が足りないか、それを満たすのに何分かかるか、 満たさずに進めた場合に何が起きるかを具体的に示し、判断材料を渡す。
使うときの注意
- 利用者が所有・管理するサイトに対してのみ使う。
- 移行とPHPアップグレードを同時に行わない。 障害の切り分けが不可能になる。
- 判断できない状態を「たぶん大丈夫」で通さない。懸念として明示して利用者に返す。
- 復旧の見込みが立たない場合、時間を使い切る前にロールバック(RUNBOOK Phase 13)へ切り替える。 判断のデッドラインは作業開始前に合意しておく。
参照
references/RUNBOOK.md— 移行の型判定から事前調査、日程、バックアップ、ファイル/DB移行、 wp-config再作成、URL置換、hosts検証、切替、切替後検証、引き渡し、ロールバック、実行前チェックリストreferences/TROUBLESHOOTING.md— S1〜S16の症状別対処(白画面、DB接続エラー、リダイレクトループ、 パーマリンク404、シリアライズ破損、画像の旧ドメイン残存、混在コンテンツ、noindex残存、 robots.txt、DNS未浸透、メール不達、cron停止、ログイン不可、置換が反映されない)
MIT License. Provided as-is, without warranty. Part of PENGIN Tools.