アプリ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つの性質を持たせました。

  1. 手で書いた名前の一覧。 globも wp-* のようなパターンも使いません。共用ルートでパターン削除をすると、何年も前に誰かが作った wp-backup-of-the-app-api のようなフォルダまで巻き込みかねません。
  2. 拒否リスト。 一覧の中に保護対象の名前(APIのフォルダ、アプリのフォルダ、言語フォルダ、index.html、.htaccess、ads.txt、app-ads.txt)が混じっていないことを最初にassertします。確認できるのは一覧そのものだけですが、問題になりやすい編集ミスはこれで防げます。
  3. 削除ではなくリネーム。 各項目を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

チェックリスト

  1. 本当にロールバックできない状態か確認する(DBは削除済みか)。まだ戻す可能性があるなら、ファイルは残しておく。
  2. アプリのソースを wp-content、wp-json、xmlrpc、wp-admin でgrepする。
  3. .user.ini や php.ini に auto_prepend_file がないか確認する。
  4. ルートの index.php に WP_USE_THEMES があること、DirectoryIndex で静的なトップページが先に返ることを確認する。
  5. ホスティング会社が作ったファイルも含めて、削除対象を手で書き出し、保護対象の名前と照合する。
  6. すべてWebルートの外へリネームし、回帰チェックを流し、そのあとで削除する。
  7. 新しい .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のままにしました。

  • #WordPress
  • #htaccess
  • #410
  • #セキュリティ
  • #共用サーバー
  • #FTP
  • #LiteSpeed

t.laboratory

実際に作って運用しているアプリ・サーバー・サービスで起きた問題と、その解決に使ったコードを記録する技術ブログです。

運営者情報 · アプリ

↑