格闘の記録 〜13年前のWordPressを復旧〜

最終更新日

以前、10年以上放置したWordPressから何が出てきたかを書いた。

今回は復旧作業そのものの記録である。手順というより、想定と実際がどこでずれたかの記録になっている。

作業前に自分で引き継ぎ文書を書いた。症状、原因、環境の制約、対処手順。そこに書いた前提が3つ、事実と違っていた。

なお私は電気回路の出身で、Webサーバの運用は本業ではない。


前提1: 症状の記載が古かった

文書にはこう書いていた。

Your PHP installation appears to be missing the MySQL extension

実際の応答はこうだった。

HTTP/1.1 500 Internal Server Error
(本文 0 バイト)

エラーメッセージが出なくなっている。

引き継ぎ文書の「現在の症状」は、書いた時点のスナップショットである。作業再開時には自分で取り直す。


前提2: ファイルの移動が完了していなかった

文書の記載。

WordPress本体一式をルートから公開フォルダへ移動

実際にはルートに残っていた。

~/web/
├── wp-admin/         残っている
├── wp-includes/      残っている
├── wp-content/ 319MB
└── mixture/          公開フォルダ
    ├── wp-config.php ○
    ├── .htaccess ○
    ├── wp-*.php ○
    ├── wp-admin/     無い
    ├── wp-includes/  無い
    └── wp-content/   無い

ブラウザのFTP画面でディレクトリを個別にコピーしていて、サイズの大きい3つの手前で作業が止まっていた。

wp-settings.php はあるが wp-includes/load.php が無い。require が失敗してfatal errorになり、0バイトの500になる。前提1の症状変化はこれが原因だった。

移動が完了していなかったため、ルート側の wp-content 319MBはそのまま残っていた。移動として完遂されていた場合、失敗時に残らなかった可能性がある。

数千ファイルの転送はブラウザのFTP画面では現実的でない。この時点でCLIに切り替えた。


前提3: PHPバージョンの下限はドメイン単位だった

文書の記載。

PHPバージョン 7.4(CGI版)。7.4が下限で、これ以下には戻せない

これは管理画面のプルダウンを見て書いたものである。実際、7.4より下の選択肢は表示されない。

同一アカウント上の他ドメインを実測した結果。

ドメイン PHP 状態
復旧対象 7.4.33 停止中
別サイトA 5.4.45 正常稼働
別サイトB 5.3.29 正常稼働
別サイトC 8.3.33 正常稼働

PHPバージョンはドメイン単位で設定されており、同一アカウントに5.3と8.3が同居していた。「7.4が下限」は対象ドメインの選択肢についての記述であり、アカウント全体の制約ではない。一度上げると戻せない仕様が、そのドメインにだけ適用されている。

共有ホスティングの制約は、アカウント単位・ドメイン単位・ディレクトリ単位で異なる。どのスコープの話かを確認する必要がある。


同じDBで片方だけ接続できなかった理由

復旧対象と別サイトAは同じデータベースを使っている。認証情報も同じである。片方だけ接続できない。

原因はPHPバージョンだった。PHP 5.4の ext/mysql は旧式の認証プロトコルに対応しているが、PHP 7のmysqlndは対応していない。

根本にあるのはこれである。

mysqldump: Got error: 2049: Connection using old (pre-4.1.1)
authentication protocol refused

DBユーザーのパスワードが old_password 形式で保存されていた。

同じ認証情報で mysql コマンドは接続できていた。mysql クライアントは secure_auth がOFF、mysqldump はONという違いによる。エラーメッセージはクライアントのオプションを指しているが、原因はサーバ側に保存されているハッシュの形式である。


パスワードの再設定では解決しない

引き継ぎ文書に書いていた対処法。

PHP 7.1以降では old_password 形式が使えない。 対処: 管理画面でパスワードを再設定して native_password 形式に更新する

このサーバでは効かない。確認は1行で済む。

SELECT LENGTH(PASSWORD('probe')), LENGTH(OLD_PASSWORD('probe'));
-- 16, 16

新形式なら41バイト、旧形式なら16バイトになる。両方16だった。

SHOW VARIABLES LIKE 'old_passwords';
-- old_passwords = ON

このMySQL 5.1は、新しく設定したパスワードも旧形式で保存する。管理画面でリセットしてもPHP 7.4からは接続できない。

この時点で「パスワードを直す」という選択肢が消え、DBサーバの移行が必要になった。当初の計画には無かった工程である。

パスワードリセット系の対処法は、サーバがどの形式で保存するかを確認しないと空振りする。SELECT LENGTH(PASSWORD('任意の文字列')) で判別できる。


影響範囲の確認

DBを変更するにあたり、他サイトへの影響を確認した。

7サイトすべての wp-config.php でDBユーザー名が同じ文字列だった。同一ユーザーを共有しているように見える。

平文を残さずに比較するため、md5の先頭で突き合わせた。

DB パスワード(md5先頭) DBホスト 使用サイト
A 7fe38c58… mysql517 5サイト
B eec6d786… mysql025 2サイト
C 21b4ea10… mysql132 1サイト

DBごとに別だった。ユーザー名の文字列が一致しているだけで、DBホストも異なるため実体は別ユーザーである。

影響範囲は7サイトから5サイトに縮小した。

比較のみが目的なら平文を出す必要はない。値を知る必要がなく、同一かどうかだけ判定すればよい場面は多い。


サーバのshellで使えるコマンド

CLIに移行して最初に環境を確認した。

コマンド 有無
tar gzip unzip rsync find あり
mysql mysqldump あり(5.6.51)
php(CLI) なし
mktemp なし
/dev/stderr 使用不可

php CLIが無いためWP-CLIは使えない。方針を決める前に確認しておく情報である。

mktemp が無いのでテンポラリファイルはパスを固定して作成する。2>>/dev/stderr は失敗してパイプごと終了するため、ファイルに出力する。

共有ホスティングのshellは標準的なLinux環境とは異なる。作業前に command -v で棚卸ししておく。


移行先はMySQL 8.4、クライアントは5.6

old_passwords の解決として新規DBを作成したところ、MySQL 8.4が割り当てられた。PHP 7.4のmysqlndは対応している。

一方、サーバ上の mysql クライアントは5.6.51である。8.4のデフォルト認証方式に対応していない可能性があった。

対策として、手元のMacに新しいクライアントを入れ、SSHポートフォワード経由でインポートできる経路を用意した。結果的には5.6のクライアントでも接続できたため使わなかった。

移行時はサーバ側だけでなくクライアント側のバージョンも確認する。


wp-content/db.php ドロップイン

コア入れ替え前に wp-content/ を確認したところ、db.php が存在した。

WordPressのドロップインで、2011年製のキャッシュプラグインがDB接続層を独自クラスに差し替えていた。

$GLOBALS['wpdb'] = new dbrc_wpdb( DB_USER, DB_PASSWORD, DB_NAME, DB_HOST );

コアを更新しても、これが残っていると単独で動作しなくなる。PHP 8で削除された定数定義の書き方も使われていた。

wp-content/ 直下の db.php object-cache.php advanced-cache.php は、プラグイン一覧に表示されないが優先的に読み込まれる。プラグインを全停止しても症状が消えない場合は、これらを確認する。

リネームして無効化した。クエリキャッシュのみの機能であり、可逆である。


日本語版パッケージの翻訳ファイル

引き継ぎ文書の記載。

rm -rf wordpress/wp-content   # 既存のテーマ・プラグインを守るため

新しいコアの wp-content で上書きするとテーマ・プラグイン・アップロード画像が消えるため、この判断自体は正しい。

ただし日本語版パッケージでは翻訳ファイルが wp-content/languages/ に含まれる。

wordpress/wp-content/languages/ja.mo         413KB
wordpress/wp-content/languages/admin-ja.mo
wordpress/wp-content/languages/ja-*.json

これを削除すると4.9時代の古い .mo が残り、管理画面の表示が一部英語になる。

languages/ のみを別に切り出し、既存のディレクトリへマージした。

wp-content = ユーザーデータ」は英語版パッケージの前提である。作業前に unzip -l で中身を確認する。


WordPressの自動更新が先に走っていた

コアを5.9に置き換えた後、upgrade.php でDBスキーマを移行する予定だった。

実行前に db_version を確認したところ、すでに5.9が要求する値になっていた。

generator タグを確認すると、配置したのは5.9で、動作しているのは5.9.16だった。ファイルの更新時刻は配置の5分後である。

WordPressのバックグラウンド自動更新が、初回アクセス時のWP-Cron経由で実行され、コアの自己更新、upgrade.php?step=upgrade_db によるスキーマ移行、完了通知メールの送信までを行っていた。

WordPress 3.7以降、マイナーリリースの自動更新は既定で有効である。古いバージョンを配置しても、初回アクセスでパッチが適用される。

5.9には既知の脆弱性があるため、5.9.16まで上がったこと自体は望ましい。ただし復旧作業中は、自分の操作と自動処理の結果が混在する。判別できたのはファイルのタイムスタンプのみだった。


実際の作業手順

引き継ぎ文書に書いた計画。

1. バックアップ
2. コアを差し替え
3. upgrade.php でスキーマ移行
4. 最新版へ更新

実際の手順。

1. バックアップ(ファイル + DB)
2. 新規DB(MySQL 8.4)を作成し29テーブルを移行      ← 追加
3. wp-config.php を新DBへ向ける                    ← 追加
4. wp-content をルートから公開フォルダへコピー      ← 追加
5. db.php ドロップインを無効化                     ← 追加
6. コアを配置 + 翻訳をマージ
7. プラグインを全停止した状態で upgrade.php        ← 変更
8. 表示確認 → プラグインを1つずつ復帰
9. 最新版へ更新

5工程増えた。うち3つは、引き継ぎ文書の前提が事実と違ったことに起因する。

3つとも、確認方法まで書いていれば防げた種類の誤りだった。「7.4が下限」ではなく「管理画面のプルダウンに7.4より下が表示されない」と書いてあれば、スコープの取り違えに気づける。引き継ぎ文書には結論だけでなく、その根拠を併記する。

次の記事では、この作業で最も時間を使った部分を書く。判定が4回誤っていた件である。


参考

  • MySQL リファレンスマニュアル: old_passwords / secure_auth
  • WordPress: バックグラウンド自動更新の設定について
  • WordPress: ドロップイン(db.php / object-cache.php / advanced-cache.php
  • WordPress: 過去バージョンのリリース一覧

この文章は原案・ディレクション:九頭龍、作文:AIで書いています。

九頭龍 'kuz' 雄一郎 エンジニア/経営者, 日本の大企業からシリコンバレーのスタートタップまで多種多様な千尋の谷に落ちた経験を持つ。 株式会社ClayTech Founder/CEO, 監査役DX株式会社 Co-founder/CTO, 株式会社スイッチサイエンス取締役, 株式会社2nd-Community取締役, 東北大学客員教授, 東京工業大学非常勤講師, 武蔵野美術大学非常勤講師, 他複数社の顧問など。

シェアする