skills/wordpress-migration/references/RUNBOOK.md
Workspace snapshot · 09/04 13:33
WordPress 移行 RUNBOOK
事前調査からロールバックまでの正本手順。SKILL.md から参照される。
Claude Code はこのファイルを読んでから実行計画を利用者へ提示する。
前提:
- 対象は利用者が所有・管理するサイトのみ。
- DNS変更・サーバー契約・決済は利用者本人が実施する。代行しない。
- 破壊的操作(DB書き換え、ファイル削除、DNS切替)の前に、必ず実行内容と影響範囲を利用者へ提示して同意を取る。
- 「たぶん大丈夫」で進めない。判断できない状態は WARN として明示し、利用者に返す。
Phase 0. 移行の型を決める
最初に、この移行がどの型かを確定させる。型が違うと手順が変わる。
| 型 | ドメイン | サーバー | URL置換 | 主なリスク |
|---|---|---|---|---|
| A. サーバー移行のみ | 変わらない | 変わる | 不要 | DNS切替のタイミング、旧サーバーへの書き込み分岐 |
| B. ドメイン変更のみ | 変わる | 変わらない | 必要 | シリアライズ破損、リダイレクト設計、検索評価の移転 |
| C. サーバー+ドメイン | 変わる | 変わる | 必要 | A と B の全部 |
| D. ステージング本番化 | 変わる | 同一/別 | 必要 | 検索避け(blog_public)の残存、Basic認証の残存、テスト用メール設定の残存 |
| E. HTTPS化のみ | 同一ホスト | 変わらない | 必要(http://→https://) | 混在コンテンツ |
型を利用者の記憶だけで決めず、Phase 1で取得する現 siteurl / home と、
利用者が申告した移行先URLの差分から判定して提示する。
Phase 1. 事前調査
次の読み取り専用コマンドを対象WordPressのルートで実行し、結果を preflight-report.md に記録する。
wp core version
wp eval 'echo PHP_VERSION, PHP_EOL;'
wp db query 'SELECT VERSION();' --skip-column-names
wp option get siteurl ; wp option get home ; wp option get blog_public
wp config get table_prefix --type=variable
wp core is-installed --network && echo multisite || echo single-site
wp rewrite structure
wp plugin list --status=active
wp theme list --status=active
wp db size --tables
du -sh wp-content/uploads
結果を読み、次を確定する。コマンドが使えない項目は「OK」にせず、未確認として記録する。
- WP / PHP / MySQL(MariaDB) バージョン 移行先のPHPが移行元より新しい場合、古いテーマ・プラグインが致命的エラーを出す。 PHP 7.x → 8.x は非互換が出やすい。移行先で先に PHP バージョンを移行元に合わせ、 移行完了後に別作業としてPHPを上げる。移行とPHPアップグレードを同時にやらない。
siteurl/home— 値が食い違っている場合、その状態を再現するのか正すのかを先に決める。- テーブル接頭辞 —
wp_以外なら、wp-config.phpの$table_prefixを移行先でも一致させる。 - マルチサイトか — マルチサイトは本RUNBOOKの単体サイト手順では足りない。
wp search-replace --network、wp_blogs/wp_site/wp_sitemetaのドメイン更新、wp-config.phpのDOMAIN_CURRENT_SITE更新が追加で必要。サブドメイン型はワイルドカードDNSとSSLも必要。 - パーマリンク構造 — 移行後に必ず同じ構造へ戻す。
/%postname%/などをメモに残す。 - DBサイズ / uploads サイズ
- uploads が数GB級、またはDBが数百MB級なら、ワンクリック移行プラグインは 実行時間上限・POSTサイズ上限・メモリ上限に当たる。 最悪なのは失敗ではなく「途中まで成功して完了扱いになる」こと。 CLI手順(Phase 5以降)を選ぶ。
- 移行を壊す
wp-config.php定数 —WP_SITEURL/WP_HOME/WP_CONTENT_URL/COOKIE_DOMAIN/DOMAIN_CURRENT_SITE/FORCE_SSL_ADMIN/WP_CACHE/DISALLOW_FILE_MODS。 これらが定義されているとDBの値は無視される。URL置換をしたのに変わらない事故の主因。 - キャッシュ系・セキュリティ系プラグイン — 移行前に停止対象として洗い出す(Phase 4)。
blog_public— 0 なら検索避けが有効。移行先で公開する場合は 1 に戻す。
利用者に必ず確認する(推測で埋めない)
- 移行元/移行先のホスティング事業者と、SSH または WP-CLI が使えるか
- ドメインは変わるか(型 A〜E のどれか)
- コンテナ/権威DNSの管理場所と、レコードを変更できる人は誰か
- コンテンツ凍結できる時間帯(記事投稿・受注・フォーム送信を止められる窓)
- 旧IP・旧ドメインに依存している仕組み: 送信メール(SPF/DKIM/DMARC)、決済のIPホワイトリスト、 外部APIのリファラ/オリジン制限、CDN、外部からのcron呼び出し、Webhook登録先URL
- ECサイトか会員サイトか(注文・会員データが移行中に増えるなら、凍結が必須)
Phase 2. 日程に落とす(当日にやると間に合わないもの)
| 作業 | いつ | 理由 |
|---|---|---|
| DNS の TTL を下げる(3600 → 300) | 切替の48〜72時間前 | TTL変更自体が旧TTLの分だけ浸透待ちになる |
| 移行先でSSL証明書を発行 | 切替前 | DNSが向く前に発行できない方式(HTTP-01)なら、切替直後に無防備な時間ができる。DNS-01 か、事業者の事前発行機能を使う |
| 移行先サーバーの契約・PHP設定 | 切替前 | 当日に環境差異が出ると詰む |
| コンテンツ凍結の告知 | 前日まで | 編集者・クライアントの合意が必要 |
| 旧サーバーの解約 | 切替後、最低でも TTL 経過+7日 | 巻き戻し先が消えると復旧不能になる |
Phase 3. バックアップ(ここを省略した移行は始めない)
移行元で取得する。DBとファイルの両方。
# DB
wp db export ~/backup/pre-migration-$(date +%Y%m%d%H%M).sql
# 空でないこと・末尾まで書けていることを確認する
ls -l ~/backup/pre-migration-*.sql
tail -c 200 ~/backup/pre-migration-*.sql # "Dump completed" 相当が見えるか
# ファイル(wp-content とルート直下の設定ファイル)
tar czf ~/backup/pre-migration-files-$(date +%Y%m%d%H%M).tar.gz \
wp-content wp-config.php .htaccess
確認事項:
- ダンプが 0 バイトでない。極端に小さい(100KB未満)場合は途中で切れていないか中身を見る。
- 復元手順を書き出しておく(
wp db import FILE、gzipならgunzip < FILE | wp db import -)。 - バックアップは移行元サーバーの外(ローカル or 別ストレージ)にも1部置く。 サーバーごと落ちたときに同じサーバー上のバックアップは取り出せない。
- DBダンプと
wp-config.phpは認証情報を含む。サーバー外のコピーは暗号化し、必要な担当者だけに 読み取り権限を与え、移行完了時に削除日を決める。共有リンクを公開設定にしない。 - 「取得できた」と「戻せる」は別。 移行先または使い捨ての検証用DBへ実際にリストアして、
復元可能性を1回だけ確かめる。本番DBへ試し戻ししない。
ここで初めて、そのダンプがロールバック手段として使えると言える。 リストアできないダンプを持っているのは、バックアップが無いのと同じ。# 移行先の空DB、または検証用に作った別DBに対して行う wp db import ~/backup/pre-migration-XXXXXXXX.sql wp post list --post_type=any --format=count # 移行元の件数と一致するか wp option get siteurl # 期待値が入っているか
Phase 4. 移行元の状態を整える
- コンテンツ凍結を開始(投稿・更新・受注を止める)。
- キャッシュ系プラグイン(WP Super Cache, W3 Total Cache, LiteSpeed Cache, WP Rocket 等)を
停止する。停止せずにコピーすると、旧ドメインを埋め込んだ静的HTMLとキャッシュ設定ごと運ぶ。
wp plugin deactivate <cache-plugin> wp cache flushwp-content/cache/、wp-content/advanced-cache.php、wp-content/object-cache.phpの 有無を確認し、キャッシュ生成物は移行対象から除外する。 - セキュリティ系プラグイン(ログイン制限、ファイル改変検知、IP制限)は移行後に誤検知の元になる。 停止するか、移行後の再設定項目としてメモに残す。
- リダイレクト管理プラグインのルールを控える(移行後に旧URL→新URLを足す土台になる)。
Phase 5. ファイルを運ぶ
wp-content を中心に運ぶ。WordPressコア(wp-admin / wp-includes)は運ばず、移行先で同一バージョンを新規取得するほうが安全。
コアの改変物や古いコアを持ち込まないため。
# 移行先で同一バージョンのコアを用意
wp core download --version=<移行元のバージョン> --locale=ja
# 移行元 → 移行先(rsync が使えるなら最優先。再開できる)
rsync -avz --progress \
--exclude 'wp-content/cache/' \
--exclude 'wp-content/upgrade/' \
--exclude '*.log' \
./wp-content/ user@new-host:/path/to/new/wp-content/
- rsync が使えない場合は分割 tar(
split)で運ぶ。FTPでの数万ファイル転送は途中で欠落しても気づけない。 - 転送後にファイル数とサイズを両側で照合する。
find wp-content/uploads -type f | wc -l ; du -sh wp-content/uploads - パーミッション: ディレクトリ 755 / ファイル 644、
wp-config.phpは 600 または 640。 所有者をWeb実行ユーザーに合わせる。
Phase 6. DB を運ぶ
# 移行先でDBを用意(事業者パネル or CLI)してから
wp db import ~/pre-migration-XXXXXXXX.sql
- 文字セットは移行元に合わせる(
utf8mb4/ 照合順序utf8mb4_unicode_ci等)。utf8のDBへutf8mb4のダンプを入れると絵文字や一部漢字が壊れる。 - import 時のエラーを黙って流さない。
ERROR 1064/1273(不明な照合順序)が出たら、 ダンプの照合順序を移行先が対応するものへ置換してから再実行する。 - import 後に件数を照合する。
wp post list --post_type=any --format=count wp user list --format=count wp option get siteurl ; wp option get home
Phase 7. wp-config.php を作る(コピーしない)
移行元の wp-config.php をそのまま置かない。移行先の値で作り直す。
DB_NAME/DB_USER/DB_PASSWORD/DB_HOST(localhost以外のことがある)$table_prefixは移行元と一致させる- 認証用ソルトキー: 移行を機に再生成すると全ユーザーがログアウトされる。 引き継ぐか再生成するかを意図して決める
- Phase 1 で見つけた
WP_SITEURL/WP_HOMEなどの定数は、新URLへ更新するか削除する。 残したまま置換すると「置換したのに変わらない」状態になる - 移行検証中の一時設定として
WP_DEBUG=true、WP_DEBUG_DISPLAY=false、WP_DEBUG_LOG=trueを入れ、 公開前に必ず戻す
Phase 8. URL置換(型 B / C / D / E のみ)
型Aは実行しない。ドメインが変わらないなら置換は不要で、やれば事故が増えるだけ。
WP-CLI が使えるかを先に確定させる。 国内の共有レンタルサーバーではSSHがプラン依存で、
SSHがあってもWP-CLIは標準提供されていない(各社の公式仕様で確認できる範囲ではゼロ)。
使えない場合は SKILL.md Step 5-B の経路(DBを取り出してWP-CLIが使える場所で置換し、戻す)へ進む。
phpMyAdmin の検索置換やSQLの REPLACE() でURLを一括変換してはいけない。
SKILL.md Step 5 にある6形式の wp search-replace を、まずすべて --dry-run 付きで実行する。
件数と対象テーブルを利用者へ提示し、承認後にDBを再エクスポートしてから、同じ6コマンドだけを
--dry-run なしで同じ順序で実行する。
- 生SQLの
sed/REPLACE()で置換しない。PHPシリアライズ値の長さが壊れるため、必ずwp search-replace --preciseを使う。 - dry-run が 0 件なら、DBが違う/すでに置換済み/
--oldの指定が格納形式と一致していない。wp db search 'old.example.com' --all-tables-with-prefixで実際の出現箇所を確認する。 - 置換後:
wp cache flush wp rewrite flush --hard wp transient delete --all wp option get siteurl ; wp option get home
Phase 9. DNS切替の「前」に検証する
ここを飛ばして切替えるのが最も多い失敗。必ず切替前に新サーバーで動作確認する。
方法(いずれか):
- hosts ファイルで自端末だけ新IPへ向ける(推奨。本番URLのまま検証できる)
203.0.113.10 new.example.jp www.new.example.jp - 事業者の一時URL(
http://xxx.example-host.jp/~user/) ただし一時URLはURLが違うため、canonical・内部リンク・混在コンテンツの検証が正確に出ない。 hosts 方式が使えるなら必ずそちらを使う。
検証:
curl -sI https://new.example.jp | head -n 20
curl -sIL https://new.example.jp | grep -i '^location'
curl -s https://new.example.jp | grep -Ei 'noindex|rel="canonical"|old\.example\.com|http://'
curl -s https://new.example.jp/robots.txt
手でも見る:
- トップ、投稿1件、固定ページ1件、アーカイブ、カテゴリ、検索結果、404ページ
- フォーム送信(送信先メールが届くか。ここは移行で最も壊れやすい)
- 会員ログイン、カート・決済(テストモード)
- 管理画面(メディア一覧のサムネイル表示、プラグイン設定の中身が残っているか)
- 画像URLが新ドメインになっているか(ブラウザの開発者ツールでリクエスト先を見る)
FAILが残っている状態でDNSを切らない。
Phase 10. DNS切替
- 切替直前に、凍結期間中に増えた差分を確認する。増えていたらDBを再取得して Phase 6・8 をやり直す。
- Aレコード(またはCNAME)を新サーバーへ向ける。TTLは下げてある前提。
- 旧サーバーは止めない。TTL経過中は両方に到達しうる。 旧サーバーで投稿・受注が発生しないように、旧側は読み取り専用または閉鎖告知に切り替える (型Bでドメインが変わる場合は、旧ドメインを新ドメインへ301するのが本筋)。
- 浸透確認:
dig +short new.example.jp dig +short @8.8.8.8 new.example.jp curl -sI https://new.example.jp | head -n 20
Phase 11. 切替後の検証
Phase 9 の4つの curl 確認をDNS切替後にも繰り返し、確認したURL、HTTP状態、リダイレクト、
canonical、robots、旧ドメイン残存の結果を verify-after.md に記録する。
必ず個別に確認する:
wp option get blog_publicが 1(型Dで最も損失が大きい事故)/robots.txtにDisallow: /がない- HTMLに
noindexが残っていない、X-Robots-Tagヘッダーが付いていない - canonical が新ドメインを指している
- HTTPS が全ページで有効、混在コンテンツなし
- 旧ドメイン→新ドメインの301(型B/C)。トップだけでなく下層URLもパスを保って転送されるか
- サイトマップを検索エンジンへ再送信(Search Console。ドメイン変更ならアドレス変更ツール)
- メール送信(フォーム、パスワードリセット、注文通知)
- cron:
wp cron event list。外部cronで叩いていたURLが旧ドメインのままになっていないか - 常時SSL・キャッシュ・セキュリティ系プラグインを再有効化し、Phase 9 の確認を再実行する
Phase 12. 引き渡し
利用者へ次を明示的に報告する。曖昧に「完了しました」で終えない。
- 検証結果(FAIL / WARN / OK の件数、残ったWARNの理由と許容判断)
- 旧サーバーをいつまで残すか(最低 TTL経過+7日)と、解約予定日
- ロールバック手順とその有効期限
- 手で戻す必要がある設定の一覧(検索避け、
WP_DEBUG、キャッシュ、セキュリティ、cron、メール、Basic認証) - 移行後に別作業として実施すべきこと(PHPバージョン更新、プラグイン更新)
- 使ったバックアップファイルの場所
Phase 13. ロールバック
判断基準を先に決めておく。「切替後○分以内に主要導線が復旧しなければ戻す」を数値で合意する。
# DBを戻す
wp db import ~/pre-replace-XXXXXXXXXXXX.sql
# それでも直らない場合はファイルも戻す
tar xzf ~/backup/pre-migration-files-XXXXXXXX.tar.gz
- DNSを旧サーバーへ戻す場合、再度TTL分の浸透待ちが発生する。戻すほど時間がかかることを利用者へ先に伝える。
- 切替後に本番で発生したデータ(注文、フォーム、投稿)は、DBを戻すと消える。 戻す前に、切替後の差分を必ずエクスポートしておく。
実行前チェックリスト
- 移行の型(A〜E)を確定した
- Phase 1 の読み取り専用調査を記録し、利用者と共有した
- DBとファイルのバックアップを取得し、空でないことを確認した
- バックアップをサーバー外にも1部置いた
- TTLを下げた(48〜72時間前)
- 移行先のSSLを事前発行した
- コンテンツ凍結の窓を合意した
- 旧IP/旧ドメイン依存(メール、決済、API、cron、Webhook)を洗い出した
- キャッシュ系プラグインを停止した
-
wp-config.phpを移行先の値で作り直し、URL系定数を処理した - URL置換は dry-run を確認してから実行した
- DNS切替の前に hosts で主要URL・リダイレクト・canonical・robots・旧ドメイン残存を確認した
- 切替後に
blog_public= 1、robots.txt、canonical、301、メール送信を確認した - ロールバック手順と旧サーバー保持期限を利用者へ渡した
MIT License. Provided as-is, without warranty. Part of PENGIN Tools.