アプリAPIと同居するサーバーからWordPressを安全に消す
アプリのバックエンドも動く共用サーバーから、WordPressの残骸21項目を削除。事前に確認すべきこと、戻せる「移動してから削除」の手順、410 Goneを返す.htaccess。
WordPressから別の仕組みに移行したものの、同じサーバーにWordPressのファイルが残ったままになっている人向けの記録です。私たちの場合、そのサーバーではモバイルアプリのバックエンドも動いていました。結論はこうです。ロールバックできなくなった時点でWordPressのファイルは消します。手で書いた一覧に沿って、まずWebルートの外へ移動し、回帰チェックが通ってから本当に削除します。消したシステム用パスには410 Goneを返し、移転先がある記事には301を残します。
ブログは2026年11月8日にWordPressからPython製の静的サイトジェネレーターへ移行しました(アプリストアに登録したURLを切らずに移行した話)。このとき .htaccess からWordPressのフロントコントローラーは外しましたが、ロールバック用にファイルはサーバーに残しておきました。11月9日にWordPressのデータベースを削除したので、もうファイルだけではサイトとして動きません。残っているのは、実際のDB接続情報が書かれた wp-config.php を含む、ただの重荷と攻撃対象でした。
「wp-* を全部消す」が危ない理由
同じドキュメントルートでは、ブログ以外のものも動いています。たとえば、インストール済みのアプリ16本が起動時に呼ぶ広告設定APIです。ミスでドメイン全体のPHPが落ちたら、ブログどころではありません。そこで、手を付ける前に4つのことを確認しました。
1. WordPressのエンドポイントを呼んでいるアプリはないか。 約20本のFlutterアプリのソースを wp-content、wp-json、xmlrpc、wp-admin でgrepし、該当は0件でした。wp-content/uploads から画像を読んでいたり、REST APIを使っていたりするアプリがあれば、そのパスは残す必要があります。
2. auto_prepend_file が設定されていないか。 これがいちばん忘れやすい落とし穴です。WordPressのセキュリティ・WAF系プラグインは、こんな行を書いた .user.ini(または php.ini)を置くことがあります。
auto_prepend_file = '/home/…/public_html/wp-content/…/firewall-bootstrap.php'
PHPは、そのディレクトリ以下のすべてのPHPリクエストの前にこのファイルを実行します。WordPressへのリクエストだけではありません。wp-content を消すと、アプリのAPIも含め、ドメイン内のすべてのPHPが "Failed opening required" の致命的エラーで止まります。今回は .user.ini も php.ini も存在しなかったので、このリスクはありませんでした。もし存在するなら、まず設定行を消し、.user.ini のキャッシュが切れる(多くの環境で5分)のを待ってから、参照先のファイルを消してください。
3. ルートの index.php は本当にWordPressのものか。 共用サーバーでは、WordPressとは関係ない index.php が置かれていることもあります。ダウンロードして、WordPressのテーマ読み込みを起動する WP_USE_THEMES の行が入っていることを確かめました。この確認に失敗したら、削除スクリプトは止まるようにしてあります。静的サイトのトップページは、DirectoryIndex index.html index.php で index.html が優先されていたので影響を受けませんでした。
4. バックアッププラグインのフォルダに何か残っていないか。 移行用プラグインのバックアップフォルダが wp-content の中にありましたが、中身はプレースホルダーのファイルだけで、アーカイブは入っていませんでした。残すものはありません。
何が残っていたか
wp-admin、wp-includes、wp-content と wp-*.php 以外にも、次のものが残っていました。
| 項目 | 問題 |
|---|---|
wp-config.php |
DBのホスト・ユーザー・パスワードが平文で入っている |
xmlrpc.php |
パスワード総当たりやピンバック悪用の定番の標的 |
license.txt、readme.html |
スキャナーにWordPressのバージョンを教えてしまう |
| ホスティング会社が作ったワンクリック自動ログイン用PHP | 管理パネルの「WordPressにログイン」ボタンが作るファイルで、WordPress本体のものではない |
wp-content 内のオブジェクトキャッシュ・LiteSpeed設定 |
もう意味のないプラグイン設定 |
合計で21項目、ディレクトリ3つとファイル18個です。自動ログイン用のファイルには特に注意してください。WordPress本体には含まれないので、WordPress向けのチェックリストには出てきません。管理パネルが作ったファイルがないか、ドキュメントルートを見ておく価値があります。
手順:移動して、確認して、それから削除
削除スクリプトには3つの性質を持たせました。
- 手で書いた名前の一覧。 globも
wp-*のようなパターンも使いません。共用ルートでパターン削除をすると、何年も前に誰かが作ったwp-backup-of-the-app-apiのようなフォルダまで巻き込みかねません。 - 拒否リスト。 一覧の中に保護対象の名前(APIのフォルダ、アプリのフォルダ、言語フォルダ、
index.html、.htaccess、ads.txt、app-ads.txt)が混じっていないことを最初にassertします。確認できるのは一覧そのものだけですが、問題になりやすい編集ミスはこれで防げます。 - 削除ではなくリネーム。 各項目をFTPの
RENAMEで、Webルートの外のアーカイブフォルダへ移しました。リネームなら数千ファイルあるディレクトリでも一瞬で終わり、その時点でWebから見えなくなり、もう一度リネームすれば元に戻せます。
DIRS = ["wp-admin", "wp-content", "wp-includes"]
FILES = ["index.php", "wp-config.php", "xmlrpc.php", "license.txt", "readme.html", …]
NEVER = {"api", "app", "en", "ja", "ko", "static", "index.html", ".htaccess", "ads.txt", "app-ads.txt", …}
assert not (set(DIRS + FILES) & NEVER)
if b"WP_USE_THEMES" not in root_index_php:
sys.exit("中止: ルートの index.php が WordPress ではない")
for name in targets:
ftp.rename(f"{base}/{name}", f"{ARCHIVE}/{name}")
移動したあと、サイトのURL一覧とAPIレスポンスの比較という、すべての回帰チェックを流しました。両方が通ってから、ようやくアーカイブを削除しました。削除はバックグラウンドで、MLSD でツリーをたどりながら1ファイルずつ消していきます。FTPだと wp-includes だけでもかなり時間がかかるので、サイトの状態が分からないうちにやるべき作業ではありません。
.htaccess:システム用パスは410、記事は301
ファイルが消えると、WordPressのパスへのリクエストは404になります。これを明示的に扱うため、既存のセキュリティ用ブロックのすぐ後ろに次の2行を足しました。
# WordPress は意図して削除した: 410 Gone を返す
RewriteRule ^(wp-admin|wp-includes|wp-content|wp-json)(/|$) - [G,L]
RewriteRule ^(wp-[a-z-]+|xmlrpc)\.php$ - [G,L]
細かい点です。
(/|$)があるので、同じ文字列で始まる別の名前には当たりません。/wpx-test/や、仮に/wp-contentful/というパスがあっても対象外です。- 2行目は
.phpだけに当たります。そのため/wp-sitemap.xmlは従来どおりのルールに届き、/sitemap.xmlへの301になります。検索エンジンが覚えているかもしれないURLです。 - 旧記事のURL、カテゴリー、
/feed/、/?p=IDは、新しいページへの301のままです。これらは「消えた」のではなく「引っ越した」コンテンツだからです。 wp-content/uploads/以下の古い画像URLは410になりました。外部からリンクされている画像を移行済みなら、このブロックより前にその画像の301を書いてください。
この設定はいきなり本番に入れていません。生成した .htaccess 全体を、隔離したサンドボックス用サブフォルダに置き、RewriteBase をそのフォルダに書き換えて、19件のテストケースを流しました。結果は19/19で合格です。/wpx-test/ が404のままであること、/xmlrpc.php へのPOSTが410になること、古いuploadsの画像パスが410になることも確認しています。本番に反映したあとも同じステータスコードが返り、サイトURLチェックは53/53、APIの回帰チェックは失敗0件でした。
古い .htaccess のバックアップは参照用にサーバーに残してありますが、以前からある次のルールでWebからは読めません。
RewriteRule (^|/)\.htaccess(\.|_)[^/]*$ - [F,L]
404と410、どちらを返すか
Googleのドキュメントでは、404も410も「ページが存在しない」として扱われ、どちらもインデックスから外れます。410のほうが「意図して削除し、戻ってこない」ことをはっきり伝えるシグナルです。410のほうが少し早く消えるという話もありますが、私たちは計測しておらず、そうだとは言えません。
410を選んだのは別の理由で、自分たちのログを読みやすくするためです。/wp-login.php への404は、リンク切れなのか、タイプミスなのか、スキャナーなのか区別がつきません。410なら「意図的に消したWordPressのパス」だと分かります。そうしておけば、ログに404が出たときに、それは調べる価値があるものだと分かります。
| URLの種類 | 応答 |
|---|---|
旧記事、カテゴリー、フィード、/?p=ID |
新しいページへ301 |
/wp-sitemap.xml |
/sitemap.xml へ301 |
wp-admin/、wp-includes/、wp-content/、wp-json/ |
410 |
wp-*.php、xmlrpc.php(メソッドを問わず) |
410 |
| それ以外の存在しないURL | 404 |
チェックリスト
- 本当にロールバックできない状態か確認する(DBは削除済みか)。まだ戻す可能性があるなら、ファイルは残しておく。
- アプリのソースを
wp-content、wp-json、xmlrpc、wp-adminでgrepする。 .user.iniやphp.iniにauto_prepend_fileがないか確認する。- ルートの
index.phpにWP_USE_THEMESがあること、DirectoryIndexで静的なトップページが先に返ることを確認する。 - ホスティング会社が作ったファイルも含めて、削除対象を手で書き出し、保護対象の名前と照合する。
- すべてWebルートの外へリネームし、回帰チェックを流し、そのあとで削除する。
- 新しい
.htaccessはサンドボックスのフォルダで試してから本番に入れ、本番でも同じケースを流し直す。
よくある質問
wp-contentを消すと、WordPressと無関係なPHPまで止まることはある?
あります。.user.iniやphp.iniでauto_prepend_fileがwp-content内のファイルを指していると、そのファイルが消えた瞬間にドメイン内のすべてのPHPリクエストがエラーになります。セキュリティ系プラグインがよく設定するので、削除前に必ず確認してください。
サイトを移行したあとも消しておくべきWordPressのファイルは?
DB接続情報が入ったwp-config.php、xmlrpc.php、バージョンが分かるlicense.txtとreadme.html、ホスティング会社が作った自動ログイン用ファイル、そしてwp-admin・wp-includes・wp-contentです。今回は合計21項目を削除しました。
消したWordPressのURLは404と410のどちらを返すべき?
Googleのドキュメントでは、どちらもインデックスから削除される扱いです。410 Goneは「意図して削除した」ことをより明確に伝えます。今回はWordPressのシステム用パスに410を返し、移転先がある記事やフィードは301のままにしました。