前回は、4年放置したブログの現状を把握しました。WordPress 5.9.16、テーマは14世代前、プラグインは8個すべて4年前。PHPだけが8.2という、ちぐはぐな状態でした。
今回は、このサイトをローカル環境に複製するまでの記録です。
その前に:本番を直接触るのをやめたかった
このブログの開発環境は、これまでMAMPでした。テーマのコードを書くときはローカルで確認していたので、まったくのぶっつけ本番だったわけではありません。
ただ、そのMAMP環境は4年前に作ったきり放置されていて、いまの本番と同じ状態が再現できるものではありませんでした。ちょっとした修正のときは、結局そのまま本番のファイルを触ってしまうこともありました。
個人のブログで、記事も少なく、更新も稀。それで4年間なんとかなっていたわけです。
ただ今回のように大きなアップグレードをするとなると、この体制では無理があります。
- 途中で壊れたら、サイトが落ちたまま復旧作業をすることになる
- 「試してみる」ができない。試した瞬間が本番反映
- 何が原因で壊れたのか切り分けられない
WordPressを5世代、テーマを14世代上げる作業をぶっつけ本番でやるのは、さすがに無謀です。まずは本番と同じ状態を再現できるローカル環境を用意することにしました。
なお、ローカル環境はDDEVで構築しています。MAMPから乗り換えた理由と、その過程で遭遇したエラーについては別記事に書きました。

SSHで接続できるようにする
XserverはSSH接続に対応しています。ファイルの取得もデータベースの操作も、FTPよりコマンドのほうが速く確実なので、まずここから設定しました。
サーバーパネルでの設定
- サーバーパネルの「SSH設定」を開き、状態をONにする
- 同じ画面の「公開鍵認証用鍵ペアの生成」タブでパスフレーズを入力し、鍵を生成する
.keyファイルがダウンロードされる
パスフレーズは必ず設定して、パスワード管理ツールに保存しておいてください。この鍵はサーバーへの入口そのものです。
Mac側での設定
ダウンロードした鍵を ~/.ssh/ に置きます。
mkdir -p ~/.ssh
chmod 700 ~/.ssh
mv ~/Downloads/サーバーID.key ~/.ssh/xserver.key
chmod 600 ~/.ssh/xserver.keychmod 600 は必須です。 権限が緩いと、SSHは鍵ファイルの読み込みを拒否します。
毎回長いコマンドを打たなくて済むよう、~/.ssh/config に接続情報を書いておきます。
Host xserver
HostName サーバーID.xsrv.jp
User サーバーID
Port 10022
IdentityFile ~/.ssh/xserver.key
AddKeysToAgent yes
UseKeychain yesこれで ssh xserver だけで接続できます。
ポートは22ではなく10022 です。ここは最初に必ず引っかかるところなので注意してください。
UseKeychain yes を入れておくと、初回にパスフレーズを入力すればmacOSのキーチェーンに保存され、次回以降は聞かれなくなります。
なお、~/.ssh/config にすでに他の設定が書かれている場合は、末尾に追記してください。ただし Include で始まる行がある場合、それらは Host ブロックより前にある必要があります。
ファイルを取得する
接続できたら、wp-content をまるごと取ってきます。WordPress本体は不要です。あとでコマンド一発で入るので、その分だけ転送が速く済みます。
mkdir -p ~/Sites/blog && cd ~/Sites/blog
rsync -avz --exclude 'upgrade' --exclude 'cache' --exclude '*.log' \
xserver:ドメイン名/public_html/サブドメイン/wp-content/ ./wp-content/除外しているのは、更新作業用の一時ディレクトリとキャッシュ、ログです。どれもローカルでは不要ですし、セキュリティ系プラグインのログは数百MBに膨れることもあります。
画像を含めて130MBほどあったので、数分かかりました。
データベースを取得する
前回書いたとおり、XserverのSSHで使えるPHPは5.4なのでWP-CLIが動きません。データベースのエクスポートはphpMyAdminから行いました。
- サーバーパネルからphpMyAdminにログイン
- 対象のデータベースを選択
- 「エクスポート」タブ → 方法は「詳細」
- フォーマットはSQL、圧縮なし
- 「DROP TABLE / DROP VIEW を追加」にチェック
最後のチェックは入れておくことをおすすめします。ローカルで何度もインポートし直すときに、毎回クリーンな状態から始められるからです。段階的にアップグレードしていく作業では、必ず戻す場面が来ます。
ダンプは4.2MBほどでした。記事7本のサイトとしては、リビジョンとプラグインのログが積み重なった結果でしょう。
ローカルで起動する
DDEVでプロジェクトを作ります。本番と同じPHP・MySQLのバージョンを指定するのが重要です。
ddev config --project-type=wordpress --php-version=8.2 --database=mysql:8.0 --docroot=.
ddev startいきなり新しいPHPを指定したくなりますが、まず本番と同じ状態を再現するのが先です。そうしないと、問題が起きたときに「移行の問題」なのか「バージョンの問題」なのか切り分けられません。
WordPress本体をダウンロードします。ここも本番と同じバージョンです。
ddev wp core download --version=5.9.16 --locale=ja --skip-content--skip-content は wp-content を上書きしないための指定です。すでにrsyncで取得済みなので必須です。
データベースをインポートします。
ddev import-db --file=ダンプファイル.sqlここでひとつ注意点があります。本番の wp-config.php は持ってこないでください。 データベースの接続情報が本番のものなので繋がりません。DDEVが適切なものを自動生成してくれます。
そして起動。
ddev launch無事にサイトが表示されました。ただし画面はDeprecated警告で埋め尽くされていましたが、それはまた別の話です。
URLの置換が要らなかった
本番からデータベースを持ってくると、通常はURLの置換が必要です。wp_options テーブルなどに本番のURLが保存されているためです。
wp search-replace 'https://本番のURL' 'https://ローカルのURL' --all-tablesところが今回、これを実行する前に確認してみたところ、すでにローカルのURLが返ってきました。
DDEVが wp-config-ddev.php の中でURLを上書きしてくれていたためです。手順がひとつ減りました。
本番直接編集の痕跡
ローカル環境ができたので、次はGit管理を立て直します。実は過去に一度Gitで管理していて、GitHubにリポジトリが残っていました。
過去のコミットは5つ。OGP画像の自動生成を実装したときのものです。この履歴は残したかったので、リポジトリを取ってきて、いま本番から取得したファイルと比較してみました。
diff -r 過去のリポジトリ/swell_child 今回取得したswell_child --exclude=.git差分は2ファイルだけ。しかも、どちらも中身は同じでした。
インデントが消えている
1つ目は header.php です。
< //記事詳細ページ:
< //サムネイル登録がなければ、動的にOG画像を生成する。
< if (is_single()) {
< $thumb = get_the_post_thumbnail();
---
> //記事詳細ページ:
> //サムネイル登録がなければ、動的にOG画像を生成する。
> if (is_single()) {
> $thumb = get_the_post_thumbnail();コードは完全に同じですが、インデントだけが消えています。 左(Git側)は字下げがあり、右(本番から取得したもの)はすべて行頭から始まっている。
これは本番を直接編集した痕跡です。WordPressの管理画面のテーマエディタや、FTPソフト付属の簡易エディタで保存すると、こういう崩れ方をすることがあります。
改行コードが変わっている
2つ目は style.css です。diffでは1〜14行目すべてが差分として表示されましたが、目で見比べると文字は完全に一致しています。
こういうときの原因はだいたい改行コードです。確認してみます。
file 過去のリポジトリ/style.css 今回取得したstyle.css過去のリポジトリ/style.css: Unicode text, UTF-8 text
今回取得したstyle.css: Unicode text, UTF-8 text, with CRLF line terminators本番側が CRLF(Windows形式)になっていました。macOSとサーバーはLFが標準なので、どこかでWindowsのエディタか、FTPソフトの設定で変換されたのだと思います。
改行コードが違うと、Gitはファイル全体を「変更された」と判定します。1行も実質的に変わっていないのに、全行が差分になるわけです。
判明したこと
つまり、本番のファイルよりGit側のファイルのほうが状態が良いということでした。同じコードで、インデントが保たれていて、改行コードも正しい。
なので本番から取ってきたファイルは捨てて、Git側を正とすることにしました。
git checkout -- swell_child/もし本番だけに存在する修正があったら面倒でしたが、幸いその心配はありませんでした。4年間ほとんど触っていなかったのが、この点では幸いしたことになります。
Git管理を立て直す
過去のリポジトリを見ると、themes ディレクトリをルートとして管理していました。ただし .gitignore がなく、SWELL本体が丸ごとコミットされていました。
これは変更することにしました。理由は2つあります。
- SWELLを最新版に上げると、数千ファイルの差分が出る。自分が書いたコードの変更がその中に埋もれる
- 有料テーマのコードをリポジトリに置く必要がない。会員サイトからいつでも再取得できる
追跡から外します。
git rm -r --cached swell/--cached を付けると、Gitの管理から外すだけでファイル自体は消えません。 テーマはそのまま動き続けます。
.gitignore はホワイトリスト方式にする
最初は「除外したいものを列挙する」ブラックリスト方式で書いていました。
# SWELL本体(有料テーマ。会員サイトから再取得可能)
/swell/
# WordPressが自動生成
index.php
# OS
.DS_Storeところが後日、WordPressをアップグレードした際にデフォルトテーマが自動的に追加され、それを git add -A で巻き込んでコミットしてしまいました。388ファイル、12.6MB。プッシュまでしていたので、履歴に残っています。
そこで、こう書き換えました。
# 自作テーマ以外はすべて管理外
/*
!.gitignore
!VERSIONS.md
!swell_child//* で全部を無視して、! で必要なものだけ許可する形です。これなら、今後どんなファイルが増えても勝手にコミットされることはありません。
除外すべきものを列挙するより、含めるものを列挙するほうが安全です。何が増えるか予測できない環境では特にそうだと思います。
バージョンの記録を残す
SWELL本体をGit管理から外すと、「どのバージョンで動いていたか」の情報が失われます。これはテキストで残すことにしました。
## SWELL(親テーマ・Git管理外)
| 日付 | バージョン | 備考 |
|---|---|---|
| 2022-02 | 2.5.8.5 | 初期導入 |
## WordPress
| 日付 | バージョン | 備考 |
|---|---|---|
| 2022-01 | 5.9 | 導入 |
| 2026-08 | 5.9.16 | セキュリティ自動更新のみ |
## サーバー環境
- PHP 8.2.30
- MySQL 8.0VERSIONS.md というファイルを作って、リポジトリに置いています。数千ファイルを抱えるより、はるかに実用的です。作業を進めるたびに追記していけば、そのまま作業ログにもなります。
戻れる地点を作る
アップグレードを始める前に、いつでも戻れる状態を作っておきます。
DDEVにはデータベースのスナップショット機能があります。
ddev snapshot --name=wp5916-original数秒で終わります。何をやらかしても、これで戻せます。
ddev snapshot restore wp5916-originalこの安心感が、ローカル環境を持つ最大の利点だと思います。本番では絶対に手に入りません。
ファイル側はGitで管理しているので、コミットしておけば同様に戻せます。
ここまでで得られたもの
- 本番と同一構成のローカル環境(WordPress 5.9.16 / PHP 8.2 / MySQL 8.0)
- 過去の履歴を保ったままのGitリポジトリ
- データベースのスナップショット
- バージョンの記録
これで、壊しても戻せる状態ができました。ようやくアップグレードに取りかかれます。
次回はいよいよ本題です。テーマを14世代、WordPressを5世代上げていきます。何をどの順番で上げるべきかという原則と、一気に上げずに刻む理由について書きます。
それから、前回のタイムスタンプの話も回収します。plugins ディレクトリの更新日が当日になっていたので「プラグインの自動更新が動いている」と判断したのですが、これが間違いでした。 実際にはプラグインは4年前のまま、1つも更新されていなかったのです。ではあの日付は何だったのか、という話です。
このシリーズの第1回はこちらです。

