こんにちは、合同会社recipeです。
今回は、ConoHa WING の自動バックアップからデータベースをリストアしたのに、復元した DB が管理画面のどこにも出てこない──という状況にハマり、脱出するまでの記録です。
結論から言うと、原因は「ConoHa WING のコントロールパネルが DB・DB ユーザーの一覧を先頭 100 件しか描画しない」ことでした。DB を数百個運用しているアカウントだと、リストアで作られた DB も、それに権限を付けたいユーザーも、一覧に出てきません。同じ壁に当たった方の助けになれば幸いです。
◆ 何が起きたか
ある WordPress サイトで、投稿に紐づくカスタムフィールド(wp_postmeta の値)が 30 件まとめて空になっていることに気づきました。WordPress のリビジョンはカスタムフィールドを保存しないので、リビジョンからは戻せません。頼りになるのは サーバーの自動バックアップ だけです。
ConoHa WING には 14 日分の自動バックアップがあり、「サーバー管理 → 自動バックアップ」から日付を選んで DB をリストアできます。ここまでは公式の手順どおりで、リストア履歴にも「完了」と出ました。
ところが、サイトを見ても何も変わっていない。8 日分、7 日分、さらに遡って 2 日分と 3 回リストアしても、カスタムフィールドは空のままでした。
◆ ConoHa WING の DB リストアは「別名の DB を作る」だけ
まず押さえておくべき仕様がこれです。ConoHa WING の DB リストアは、稼働中の DB を上書きしません。リストア履歴の説明にもこう書いてあります。
DBの場合は新たなデータベースを作成し、復元したデータを保存します。
つまり、xxxxx_mydb をリストアすると xxxxx_mydb_20260916_1 のような 日付+連番つきの別 DB が作られるだけで、WordPress はまだ元の DB を見ています。使うには次のどちらかが必要です。
wp-config.phpのDB_NAMEを復元先の DB 名に書き換える(サイト全体をその時点に巻き戻す)- 復元先 DB から必要な行だけを稼働 DB に SQL でコピーする(他のデータを巻き戻さない)
そして、どちらの方法でも DB ユーザーに復元先 DB への「接続許可」を手動で付ける 必要があります。リストアしただけでは、どのユーザーからも復元先 DB は開けません。phpMyAdmin のツリーにも出ませんし、SSH から mysql で SHOW DATABASES しても出てきません(MySQL は権限のない DB の存在を教えてくれないので、存在確認すらできません)。
◆ ハマった点:一覧が「先頭 100 件」で切れる
接続許可は「サイト管理 → データベース」のユーザー一覧から、対象ユーザーの「接続先データベース」を編集して付けます。ここで詰まりました。
私たちのアカウントは複数のサービスを載せていて、DB が 500 件・DB ユーザーが約 500 件 あります。コントロールパネルはこの一覧を 先頭 100 件しか描画しません。並び替え(作成日/ネームタグの昇順・降順)を変えても 100 件の中に入らなければ表示されず、ページ送りもありません。
試して駄目だったこと:
- 並び替えを変える → 目的の行が 100 件に入らず表示されない
- サイト管理の対象サイトを切り替える → 変わらず
- SSH で
mysqlからGRANT→ 一般ユーザー権限なので不可 - 公式 API → ConoHa の API は VPS 向けで、WING には API メニュー自体がない
◆ 解決:管理画面が呼んでいる内部 API に limit を差し込む
コントロールパネルは、画面の裏で次のようなエンドポイントを呼んでいます(ブラウザの開発者ツール → Network で確認できます)。
| 用途 | メソッド | パス |
|---|---|---|
| ユーザー一覧 | POST | /Wing/DatabaseTabPageBG/FetchUsers |
| データベース一覧 | POST | /Wing/DatabaseTabPageBG/FetchSchemas |
| 接続 DB 変更(保存) | POST | /Wing/DatabaseTabPageBG/UpdateGrant |
ポイントは、これらの API が limit / offset というページング用パラメータを受け付ける ことです。UI は limit を 100 で固定して呼んでいるだけで、大きな値を渡せば全件返ってきます。
方法A:UI に全件を描画させる(おすすめ)
一番安全なのは、パネル自身の API 呼び出しに limit を足して、パネル本来の処理で全件を描画させる やり方です。表示さえされれば、接続先 DB の変更もいつものレンチアイコンから普通に操作できます。
「サイト管理 → データベース」を開いた状態で F12 → Console に以下を貼り付けて実行します。
(() => {
if (window.__limitPatched) return 'already patched';
const XO = XMLHttpRequest.prototype.open;
const XS = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.open = function (m, u) { this.__u = u; return XO.apply(this, arguments); };
XMLHttpRequest.prototype.send = function (b) {
try {
if (this.__u && (this.__u.indexOf('FetchUsers') >= 0 || this.__u.indexOf('FetchSchemas') >= 0)
&& typeof b === 'string' && b.indexOf('limit=') < 0) {
b += '&limit=5000';
}
} catch (e) {}
return XS.call(this, b);
};
window.__limitPatched = true;
return 'patched';
})();
実行後、左メニューの「データベース」をクリックし直すと一覧が再取得され、DB もユーザーも全件 描画されます。あとは目的のユーザー行を展開し、「接続先データベース」のレンチアイコンから復元先 DB にチェックを入れて保存するだけです。
行が多くて探しにくいときは、続けてこれを実行するとスクロールしてハイライトしてくれます。
(() => {
const name = 'xxxxx_mydb'; // ← 探したいユーザー名
const lis = [...document.querySelectorAll('li.schema-detail, li.items__list')];
const hit = lis.find(li => (li.textContent || '').trim().split(/\s|\n/)[0] === name);
if (!hit) return 'not found';
hit.scrollIntoView({ block: 'center' });
hit.style.outline = '3px solid #ff5a00';
return 'scrolled';
})();
注意:ページを再読み込み(F5)するとパッチが外れて 100 件表示に戻ります。表示されている間に操作を済ませてください。
方法B:API を直接叩いて確認だけする
「そのユーザーがどの DB に接続できるのか」を値で確認したいだけなら、Console から直接 fetch する方が早いです。CSRF トークンはページ内の hidden input から取れます。
(async () => {
const serviceId = new URLSearchParams(location.search).get('serviceId');
const token = document.querySelector('input[name="__RequestVerificationToken"]').value;
const body = `serviceId=${serviceId}&__RequestVerificationToken=${encodeURIComponent(token)}&limit=5000`;
const h = { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', 'X-Requested-With': 'XMLHttpRequest' };
const [u, s] = await Promise.all([
fetch('/Wing/DatabaseTabPageBG/FetchUsers', { method: 'POST', headers: h, body, credentials: 'same-origin' }).then(r => r.json()),
fetch('/Wing/DatabaseTabPageBG/FetchSchemas', { method: 'POST', headers: h, body, credentials: 'same-origin' }).then(r => r.json()),
]);
const schemaById = Object.fromEntries(s.ResponseData.Data.ItemList.map(x => [x.SchemaId, x.SchemaName]));
const user = u.ResponseData.Users.find(x => x.UserName === 'xxxxx_mydb');
console.log('DB 総数:', s.ResponseData.Data.TotalCount, '/ ユーザー総数:', u.ResponseData.Users.length);
console.log(user.UserName, '→', user.ConnectSchemaIds.map(id => schemaById[id]));
})();
レスポンスは概ねこんな形です。ConnectSchemaIds がそのユーザーの接続許可 DB、TotalCount が本当の件数です。
// FetchUsers
{ "Result": "success",
"ResponseData": { "Users": [
{ "UserId": "…", "UserName": "xxxxx_mydb", "PhpMyAdminUrl": "…", "ConnectSchemaIds": ["…", "…"] }
] } }
// FetchSchemas
{ "Result": "success",
"ResponseData": { "Data": {
"ItemList": [ { "SchemaId": "…", "Nametag": "mydb", "SchemaName": "xxxxx_mydb", "Host": "mysqlNNNN.conoha.ne.jp", … } ],
"TotalCount": 500 } } }
◆ 接続許可を付けたあと
ここまで来れば、あとは公式手順どおりです。
- phpMyAdmin(または SSH の
mysql)にそのユーザーでログインし直すと、復元先 DB がツリーに出る - サイト全体を巻き戻してよければ
wp-config.phpのDB_NAMEを復元先の名前に変更(DB_USERはそのまま) - 一部だけ戻したければ、復元先 DB と稼働 DB を JOIN して必要な行だけ UPDATE する
今回はカスタムフィールド 30 件分だけが目的だったので、本来は 3 番のような SQL が理想でした。
UPDATE xxxxx_mydb.wp_postmeta AS live
JOIN xxxxx_mydb_20260916_1.wp_postmeta AS bk
ON bk.post_id = live.post_id AND bk.meta_key = live.meta_key
SET live.meta_value = bk.meta_value
WHERE live.post_id IN (…対象の投稿ID…)
AND live.meta_key IN ('field_a','field_b','field_c');
wp-config.php を書き換える方法を選ぶ場合は、変更前の値をコメントで残しておくと戻すのが楽です。
// define( 'DB_NAME', 'xxxxx_mydb' ); // 2026-09-17 変更前
define( 'DB_NAME', 'xxxxx_mydb_20260916_1' ); // 2026-09-17 変更後(9/8 バックアップ)
◆ 注意点
limit/offsetは 非公式の内部パラメータ です。ConoHa 側の仕様変更で使えなくなる可能性があります- 自分のアカウント・自分のコントロールパネルに対する操作に限ってください。読み取りは Console から、接続許可の保存(UpdateGrant)は UI のボタンから 行うのが安全です
__RequestVerificationTokenや各種 GUID を他人に貼らないでくださいDB_NAMEを切り替える方法は バックアップ以降の変更がすべて巻き戻ります。予約や会員データなど動きのあるテーブルがあるサイトでは、行単位のコピーを選んでください- 切り替え後、バックアップ日より後に発行した アプリケーションパスワード は新 DB に存在しないので、REST API を使うなら再発行が必要です
◆ おまけ:「いつ消えたか」はアクセスログで分かる
「どの日のバックアップに戻せばいいか」を決めるには、消えた日時が要ります。wp_postmeta には更新日時がないので DB からは分かりませんが、ConoHa WING は SSH を有効にすると ~/logs/<ドメイン>/access_log でアクセスログが読めます(約 7 日分)。
今回は「フォームの一括更新が空の値で 30 件走った」ケースだったので、admin-ajax.php への POST が分単位で固まっている箇所を探しました。
(zcat access_log.*.gz; cat access_log) \
| grep "POST /path/wp-admin/admin-ajax.php" \
| awk '{print substr($4,2,17), $1}' | sort | uniq -c | awk '$1>=5'
結果、ある日の 18:17 に 30 件の POST が 0.6 秒間隔で並んでいて、その直前に別の操作が 30 回──という形がそのまま出てきました。バックアップはその前日分を選べば十分、と判断できます。
◆ まとめ
- ConoHa WING の DB リストアは 別名の DB を作るだけ。使うには接続許可の手動設定と、
wp-config.phpの変更または SQL コピーが必要 - コントロールパネルの DB・ユーザー一覧は 先頭 100 件しか描画しない。DB を大量に持つアカウントでは復元先が見えない
- 裏の API(
FetchUsers/FetchSchemas)はlimitを受け付けるので、Console からパッチを当てれば全件表示できる - 消えた日時はアクセスログの
admin-ajax.phpの連打から特定できる
「リストアしたのに何も変わらない」で数時間溶かした身としては、ConoHa さんには一覧のページ送りか検索窓が欲しいところですが、仕様が変わるまではこの方法でしのげます。同じ状況の方の参考になれば幸いです。