タグアーカイブ WordPress

Meta PixelとコンバージョンAPIの両方を設定する理由と手順

Meta PixelとコンバージョンAPIの両方を設定する理由と手順

WordPress で Facebook のコンバージョン API(CAPI)を使う場合、ブラウザ側の Meta Pixel とサーバー側の CAPI は両方とも設定するのが Meta 社の推奨する標準構成だ。Pixel ID は両方の設定に必要であり、CAPI だけを有効にして Pixel を無効にするのは推奨されない。

Meta Pixel とコンバージョン API はなぜ両方使うのか

Meta Pixel とコンバージョン API はなぜ両方使うのか

ブラウザ側の Meta Pixel は、サイト訪問者のブラウザ上で JavaScript が動作し、ページビューや購入完了といったイベントを直接 Facebook に送信する。仕組みがシンプルで設定も容易だが、広告ブロッカーやブラウザのプライバシー制限(ITP など)によってブロックされることがある。

一方、サーバー側のコンバージョン API(CAPI)は、WordPress サーバーから直接 Facebook のサーバーにイベントデータを送信する。ブラウザの制限を受けず、より確実にデータを届けられる。ただし CAPI 単体ではブラウザ上でのユーザー行動(スクロールやボタンクリックの細かなタイミングなど)を拾いにくい。

両方を併用することで、Pixel が拾ったイベントと CAPI が拾ったイベントを Facebook 側で重複排除( deduplication )し、欠損の少ない正確なデータが得られる。これが Meta 社の公式な推奨構成であり、Pixel Cat もこの併用を前提に設計されている。

Meta Pixel のみ
ブラウザの広告ブロッカーや ITP で10〜30% のイベントが欠損
CAPI のみ
ブラウザ側の細かな行動データが拾えず、オーディエンスの精度が低下
Pixel + CAPI 併用(推奨)
両方のデータを Facebook 側で重複排除し、最も正確なコンバージョン計測が可能
欠損あり  精度低下  推奨構成

各構成で得られるデータの質と欠損リスクの違いを表した概念図。併用時に Facebook 側で重複排除が働く。

Pixel Cat で推奨される設定手順

Pixel Cat で推奨される設定手順

Pixel Cat の管理画面では「Facebook Pixel」と「Conversions API」の両方にチェックを入れて有効化する。Pixel ID は両方のセクションに同じ ID を入力する必要がある。これは CAPI がどの Pixel アカウントに紐づくイベントかを識別するために使われるもので、Pixel 側と CAPI 側がそれぞれ独立して動作しながら同じアカウントにデータを送る仕組みだ。

STEP 1 Pixel Cat 設定画面で「Facebook Pixel」を有効化し Pixel ID を入力
STEP 2 同じ画面で「Conversions API」を有効化し、同じ Pixel ID を入力
STEP 3 CAPI 用のアクセストークンを Facebook イベントマネージャから生成して入力
STEP 4 Facebook イベントマネージャの「テストイベント」タブで両方のデータ受信を確認

Pixel Cat で両方を有効化する際の設定フロー。Pixel ID は両方に同じものを入力する。

Facebook Pixel セクションの設定

Pixel Cat の「Facebook Pixel」タブを開き、「Enable Facebook Pixel」をオンにする。表示されたフィールドに Facebook イベントマネージャで確認できる Pixel ID(15桁の数字)を入力する。標準イベント(PageView や Purchase など)はデフォルトでトラッキング対象になるが、必要に応じてカスタムイベントを追加できる。

Conversions API セクションの設定

Pixel Cat の「Conversions API」タブに移動し、「Enable Conversions API」をオンにする。ここでも同じ Pixel ID を入力する。Pixel ID の入力が必須なのは、CAPI がサーバーからイベントを送信する際に「どの Pixel アカウント宛か」を特定する必要があるためだ。ブラウザ Pixel とは通信経路が異なるだけで、最終的なデータの宛先は同じアカウントになる。

次に Facebook イベントマネージャでアクセストークンを生成する。イベントマネージャの「設定」タブから「アクセストークンを生成」を選び、Pixel Cat の該当フィールドに貼り付ける。トークンは CAPI が Facebook のサーバーと認証するための鍵であり、これがないとイベントを送信できない。

重複排除の仕組みを確認する

Pixel と CAPI を両方有効にすると、同じイベント(例: 購入完了)がブラウザ経由とサーバー経由の2回 Facebook に届く可能性がある。これを防ぐために、Pixel Cat は各イベントに一意のイベント ID を付与し、両方の経路で同じ ID を送る。Facebook 側は同一 ID のイベントを重複と判断して1件として計上する。この仕組みにより、データの欠損を減らしつつ二重計上を防ぐことができる。

Pixel を無効にして CAPI だけにするとどうなるか

Pixel を無効にして CAPI だけにするとどうなるか

Pixel Cat で Facebook Pixel を無効にし CAPI のみを有効にすることは技術的には可能だ。しかしこの設定では、ブラウザ上で動作する Pixel が提供するオーディエンスデータ(サイト滞在時間やスクロール深度など)が一切取得できなくなる。これにより Facebook 広告のオーディエンス構築やリターゲティングの精度が大幅に落ちる。

また CAPI 単体では、ブラウザの Cookie に依存しない代わりに、ユーザーのブラウザ情報(ユーザーエージェントや IP アドレス)をサーバー側から送る必要がある。これを適切に処理しないと Facebook 側でイベントのマッチング率が下がり、期待したほど正確なデータが得られない場合もある。特別な理由がない限り、Pixel と CAPI の併用が基本構成だ。

設定後の動作確認とテスト方法

設定後の動作確認とテスト方法

設定が完了したら、Facebook イベントマネージャの「テストイベント」タブを開く。サイト上で実際にページを閲覧したり、テスト購入を行ったりすると、ブラウザ Pixel からのイベントとサーバー CAPI からのイベントがそれぞれ表示される。両方の経路でイベントが届いていれば正常だ。

ブラウザの開発者ツール(F12)で Network タブを確認し、Facebook のドメインに向けたリクエストが発生していることも確認できる。CAPI はサーバー間通信のためブラウザの Network タブには表示されないが、イベントマネージャ上で「サーバー」と表示されるイベントがあれば問題なく動作している。

よくある質問

Pixel ID は Pixel と CAPI で別々に取得する必要があるか

同じ Pixel ID を使う。Pixel ID は Facebook 広告アカウントに紐づく一意の識別子で、ブラウザ Pixel も CAPI もこの ID 宛にデータを送信する。別々に取得する必要はなく、イベントマネージャで確認できる1つの ID を両方に入力すればよい。

CAPI のアクセストークンはどこで取得するのか

Facebook イベントマネージャの「設定」タブ内にある「アクセストークンを生成」ボタンから作成する。トークンは一度生成すると再表示できないため、コピーして安全な場所に保管する。漏洩すると第三者にイベント送信に利用されるリスクがある。

無料の Pixel Cat プラグインで CAPI は使えるか

Pixel Cat の無料版でも CAPI の基本機能は利用できる。ただし一部の高度なイベント(カスタムイベントの詳細設定や高度なマッチング機能)は有料版限定の場合がある。まずは無料版で Pixel と CAPI の両方を有効にし、イベントマネージャでデータが届くことを確認するのがよい。

CAPI 導入後、Facebook 広告の計測はすぐに改善するか

設定後すぐにイベントの受信は始まるが、Facebook 側のデータ処理や学習には数日かかることがある。広告パフォーマンスの変化を評価する際は、少なくとも1〜2週間のデータで判断する。また広告セットのコンバージョンウィンドウ設定も併用構成に合わせて見直すと効果が出やすい。

この記事のポイント

  • Meta 社の推奨はブラウザ Pixel とサーバー CAPI の併用である
  • Pixel ID は両方の設定に同じものを使用する
  • CAPI だけの運用はオーディエンスデータの欠損で広告精度が下がる
  • 設定後はイベントマネージャのテストタブで両経路の受信を確認する
  • 重複排除はイベント ID によって自動的に行われる
URLに特定の単語を含むリンクを一括でホームページに置換する方法

URLに特定の単語を含むリンクを一括でホームページに置換する方法

URL に特定の単語(例 sandwich)を含む全リンクを一括でホームページに置換するには、Search Regex プラグインか phpMyAdmin の正規表現置換を使う。単純な完全一致検索しかできないツールでは対処できないため、部分一致の条件を指定できる方法が必須になる。

なぜ通常の「Better Search Replace」では置換できないのか

なぜ通常の「Better Search Replace」では置換できないのか

「Better Search Replace」のような一般的なプラグインは、検索文字列とまったく同じ URL しか見つけられない。旧サイトから移管した際に「sandwich-f07abf」や「sandwich/xyz」のように末尾にランダムな文字列が付与されたリンクが数百件ある場合、完全一致で一つひとつ指定するのは現実的ではない。今回のように「URL の一部が特定のキーワードで、あとは異なる」パターンには、正規表現によるパターンマッチが必要になる。

Search Regex プラグインで部分一致 URL を一括置換する

Search Regex プラグインで部分一致 URL を一括置換する

Search Regex は、正規表現を使ってデータベース内の投稿やカスタムフィールド、オプションなどを検索・置換できるプラグインだ。ドライラン(事前確認)機能がなく直接置換が走るため、必ず事前にデータベース全体のバックアップを取る必要がある。

設定手順

STEP 1 Search Regex をインストールして有効化する
STEP 2 検索パターンに http[^\s]*sandwich[^\s]* を入力
STEP 3 置換後文字列に / を入力
STEP 4 「Replace」を実行(事前に必ずバックアップを取る)

Search Regex では、検索対象のカラム(post_content、post_excerpt、guid など)やテーブルを選択できる。すべてにチェックを入れると想定外のレコードまで置換されることがあるため、最初は post_content だけに絞って実行し、結果を確認するのが安全だ。置換に成功した場合、変更の取り消しは手動で行う必要がある点を覚えておく。

検索パターンの正規表現解説

http[^\s]*sandwich[^\s]* は「http で始まり sandwich を含み空白文字が出るまでの連続した URL」を意味する。[^\s]* の部分で、sandwich の前後にどんな文字が並んでいても対象に含めることができる。これにより sandwich-f07abf も sandwich/xyz もすべて拾える。もしホームページではなく完全に削除したい場合は、置換後文字列を空欄にすればよいが、リンク切れを起こすよりもホームページへ誘導するほうが SEO 上も望ましい。

Before(エラー状態)
https://example.com/sandwich/xyz → 404
https://example.com/sandwich-f07abf → 404
After(修正後)
https://example.com/ → ホームページ
https://example.com/ → ホームページ
エラー状態  修正後

phpMyAdmin でデータベースから直接置換する方法

phpMyAdmin でデータベースから直接置換する方法

より高度な操作として、phpMyAdmin を使う方法もある。phpMyAdmin はレンタルサーバーの管理画面からアクセスできる MySQL データベース操作ツールだ。検索機能で該当するレコードを洗い出したあと、SQL の UPDATE 文で一括置換する。

特定のキーワードを含むレコードを検索する

phpMyAdmin にログインし、WordPress のデータベースを選択する。「検索」タブでキーワード「sandwich」を入力し、全テーブルを対象に検索をかける。該当行が表示されるので、どのテーブルのどのカラムに URL が含まれているかを特定できる。

SQL で一括置換をかける

影響範囲を特定したら、次のような UPDATE 文を実行する。ここでも誤操作を防ぐため、必ず事前にバックアップを取る。

UPDATE wp_posts SET post_content = 
REPLACE(post_content, 
'https://example.com/sandwich-f07abf', 
'/')
WHERE post_content LIKE '%sandwich%';

ただし、この方法は完全一致の REPLACE 関数を使うため、ランダム文字列が多様な場合は検索文字列を動的に扱えない。代わりに REGEXP_REPLACE が使える MySQL 8.0 以降の環境、または MariaDB 10.0.5 以降なら、正規表現による置換が行える。

UPDATE wp_posts SET post_content = 
REGEXP_REPLACE(post_content, 
'https?://[^\s]*sandwich[^\s]*', 
'/')
WHERE post_content REGEXP 'https?://[^\s]*sandwich[^\s]*';

この SQL は、http または https で始まり sandwich を含み空白が出るまでの URL をすべて抜き出し、ホームページ(/)に置換する。データベース全体にわたって同様の処理が必要なら、postmeta や options テーブルなどにも同じ UPDATE 文を適用する。

置換前に必ず取るべき安全策

置換前に必ず取るべき安全策

Search Regex も phpMyAdmin も、一度実行すると元に戻せない操作になる。必ず次の 3 つを行う。

  • データベース全体のエクスポート(バックアップ)を取る
  • 可能ならステージング環境で先にテストする
  • 置換後はサイト全体を巡回し、表示崩れやリンク切れがないか確認する

ステージング環境が用意できない場合は、本番で実行する前に影響範囲を最小に絞る。Search Regex であれば、最初は post_content だけを対象にし、問題なければ他のカラムを追加していくと安全だ。

よくある質問

Search Regex と「Better Search Replace」はどう使い分ける?

完全に同じ文字列しか置換できないのが Better Search Replace で、部分一致や正規表現を使いたい場合は Search Regex になる。ただし Search Regex はドライランができないので、安全を重視するなら、まず Better Search Replace で置換できる部分を先に処理し、残った複雑なパターンだけを Search Regex で片付ける手順が堅実だ。

phpMyAdmin で誤って必要なデータまで置換してしまったら?

バックアップファイルをインポートして復元する。phpMyAdmin の「インポート」タブからエクスポートしておいた SQL ファイルを選択し実行すれば、置換前の状態に戻せる。バックアップを取らずに操作してしまった場合は、復元は極めて困難になるため、作業前のバックアップは必須だ。

置換後に画像や内部リンクが壊れていないか心配だ

リンクチェッカー系のプラグイン(Broken Link Checker など)を一時的に導入し、サイト全体のリンク切れをスキャンするとよい。置換してから数時間後に確認すれば、見落としがあっても早期に気づける。スキャン後は負荷を避けるため、プラグインを無効化しておく。

URL の一部に「sandwich」を含むが、それ以外の部分は維持したい場合は?

キャプチャグループ(丸括弧)を使う。たとえば sandwich より前のパスだけを残したいなら (https?://[^\s]*)sandwich[^\s]* とし、置換後文字列に $1 を指定する。置換の条件は柔軟に調整できるため、削除する範囲や残す範囲を細かく制御できる。

Search Regex の代わりに WP-CLI で置換できる?

WP-CLI が使える環境なら wp search-replace コマンドで正規表現を扱える。ただし部分一致のためには --regex オプションを使う必要があり、さらに複雑なパターンになる。コマンドラインに慣れている開発者向けの手段といえる。

この記事のポイント

  • URL に特定の単語を含むリンクを一括置換するには正規表現が必須
  • Search Regex プラグインで部分一致をパターン指定して置換できる
  • phpMyAdmin の REGEXP_REPLACE でも同様の処理が可能
  • いずれの方法でも事前のデータベースバックアップが最優先
  • 置換後はリンク切れチェックでサイトの健全性を確認する
Events ManagerでGutenbergのイベントが公開できない時の直し方

Events ManagerでGutenbergのイベントが公開できない時の直し方

Events ManagerをGutenberg編集モードで使っているときに新規イベントが公開できず、クラシックエディタでは問題なく動作する場合、プラグインのバージョンが7.3.1〜7.3.4のいずれかであることが主な原因だ。7.3.5以降へアップデートすると修正される。

どんな操作をしたときに発生するのか

どんな操作をしたときに発生するのか

Gutenbergモードを有効にしたEvents Managerで新規イベントを作成する。すべての項目を入力して「公開」ボタンを押しても、画面が再読み込みされステータスが「下書き」のままになり、公開状態に切り替わらない。一方でクラシックエディタに切り替えると、同じ内容でも問題なく公開できるという状況だ。

この現象は、WP標準テーマ(Twenty Twenty-FourやTwenty Twenty-Five)に切り替えても、他のプラグインをすべて無効化しても変わらない。Events Manager側のGutenberg統合部分にバージョン固有の不具合が存在しているために起きる。

公開ボタンが反応しない直接の原因は何か

公開ボタンが反応しない直接の原因は何か

Events Managerのバージョン7.3.1から7.3.4には、Gutenbergエディタ上でイベントを保存する際に走るバリデーション(検証)処理に問題がある。具体的には、イベントの必須項目が正しく入力されていても内部的な検証に失敗し、「公開」操作が受理されない状態になる。

とくに繰り返しイベント(定期的な開催設定)を扱う場合、「Main recurrence set times are required(メインの繰り返し時刻設定が必要です)」という趣旨の検証エラーが返され、公開をブロックされるケースが報告されている。このバリデーションエラーは管理画面の見た目には表示されず、ブラウザの開発者ツール上で確認できる。

エラー状態 Gutenbergでイベントを保存しようとする
内部処理 EMのバリデーションAPIが検証失敗を返す(7.3.1〜7.3.4の不具合)
結果 ステータスが「下書き」のまま公開されない
公開に失敗する流れ(Events Manager 7.3.1〜7.3.4)

このデモが示すように、エディタの見た目上は正常に操作しているのに、プラグイン内部のAPI応答が原因で公開処理が止まる。Events Manager 7.3.5でこのバリデーション不具合が修正されている。

Events Managerを最新版にアップデートして修正する

Events Managerを最新版にアップデートして修正する

最も確実な解決策は、Events Managerをバージョン7.3.5以降にアップデートすることだ。管理画面から数ステップで完了する。

STEP 1 WordPress管理画面で「ダッシュボード」→「更新」を開く
STEP 2 Events Managerに更新がある場合「プラグイン」一覧から更新する
STEP 3 更新後、Gutenbergで新規イベントを作成し公開できるか確認する

サイトの運用途中でアップデートをためらう場合は、まずステージング環境(テスト用の複製サイト)でアップデート後の動作を確認すると安全だ。近年の国内レンタルサーバーでは、管理パネルからワンクリックでステージング環境を作成できるものも多い。

すぐに公開したい場合の一時的な回避策

すぐに公開したい場合の一時的な回避策

プラグインのアップデートが何らかの理由ですぐにできない場合、以下の回避策で公開できることがある。

  • 公開ボタンを2回以上連続でクリックする。1回目でいったん下書きとして保存され、2回目以降のクリックで公開状態に切り替わるケースが報告されている。
  • イベント編集画面の「Events Manager」設定パネルで、Gutenbergモードを無効にしてクラシックエディタを使う。クラシックエディタでは問題なく公開できる。

これらの回避策は、あくまでアップデート前の応急処置として使う。根本的には7.3.5以降へのアップデートが必要だ。

アップデート後も繰り返しイベントでエラーが出る場合の対処

アップデート後も繰り返しイベントでエラーが出る場合の対処

7.3.5以降でも、繰り返しイベントの設定時にまれに「Main recurrence set times are required(メインの繰り返し時刻設定が必要です)」という趣旨のバリデーションエラーが表示されることがある。このエラーは、繰り返し設定の中核となる日時情報がバリデーションAPIに正しく渡っていない場合に発生する。

まず試すべきは、繰り返し設定を一度クリアして再入力することだ。とくに開始日時と終了日時、および繰り返しパターンの「初回の日時」が空欄になっていないか確認する。カスタムコードでGutenberg有効化を制御していた場合は、そのコードが完全に削除されているかも確認する。残留したコードがAPI通信に干渉している可能性がある。

よくある質問

Events ManagerのGutenbergモードはどこで切り替えられるか

管理画面の「Events」→「設定」→「管理画面」タブにある「イベントエディタの種類」で切り替えられる。ここで「Gutenberg」を選択するとブロックエディタが有効になり、「クラシックエディタ」を選ぶと従来の編集画面に戻る。

Gutenberg有効化のカスタムコードが原因になることはあるか

過去にfunctions.phpなどへ追加したGutenberg有効化コードが削除されずに残っていると、プラグインの設定と競合して予期しない動作を起こす可能性がある。コードが完全に削除されているか、もしくはコメントアウトされているか確認する。

公開ボタンを押しても何も反応しない場合はどうすればよいか

ブラウザの開発者ツール(F12キー)の「コンソール」タブにJavaScriptエラーが表示されていないか確認する。別のプラグインがGutenbergと競合してJavaScriptエラーを起こしている場合、それが原因で公開処理が止まることがある。

クラシックエディタでは問題ないのにGutenbergだけ不具合が出る理由は何か

GutenbergはREST APIを介してデータを保存する仕組みをとっている。Events Managerのバリデーション処理も、GutenbergモードではAPI経由で実行される。クラシックエディタの場合は異なる保存経路を使うため、API側の不具合の影響を受けずに公開できる。

7.3.5はいつリリースされたのか

Events Manager 7.3.5は2026年7月下旬にリリースされ、本件のGutenberg公開不具合が修正されている。管理画面の更新通知から適用できる。

この記事のポイント

  • Events Manager 7.3.1〜7.3.4のGutenbergモードには公開できない不具合がある
  • 7.3.5以降へアップデートすると修正される
  • アップデートできない場合は公開ボタンの複数回クリックやクラシックエディタで一時回避できる
  • 繰り返しイベントでの検証エラーは設定の再入力で直ることが多い
  • 過去のカスタムコードが競合していないか確認することも大切だ
WordPressテーマのアクセシビリティ対応、思ったより簡単な理由

WordPressテーマのアクセシビリティ対応、思ったより簡単な理由

アクセシビリティ対応を難しく感じる本当の理由

アクセシビリティ対応を難しく感じる本当の理由

WordPressテーマのアクセシビリティ対応に取り組もうとした開発者の多くが、最初の段階でつまずくポイントがある。WordPress.orgのテーマリポジトリで「accessibility-ready」タグを取得するための要件文書だ。WP TavernのポッドキャストでJessica Lyschik氏が指摘したように、この要件文書は初めて読む人にとって「暗号的」に映りがちだ。

問題の核心はドキュメントの書き方にある。要件には「こうあるべき」という達成目標と簡単なテスト手順は書かれているが、具体的にどのような技術的実装をすればよいのかが明示されていない。例えば「適切なHTML5タグを使用すること」という趣旨の要件があっても、header、footer、main、section、asideの各タグをどう配置すべきかまでは書かれていない。

Lyschik氏自身もこの問題を実感した一人だ。彼女が管理するテーマを新要件に合わせて見直した際、アクセシビリティの知識を何年も積んできた自分ですら「なるほど、こういう意味だったのか」と再確認する場面があったという。ましてやアクセシビリティに初めて触れる開発者にとっては、抽象的な要件と実際のコードを結びつけること自体が大きな障壁になる。アクセシビリティは「難しい」のではなく、「何をすればいいかが分かりにくい」分野なのだ。

現在のドキュメント(Before)
「テーマは適切なHTML5セマンティック要素を使用しなければならない」
要件は分かるが実装方法が不明
理想的なドキュメント(After)
「テーマは適切なHTML5セマンティック要素を使用しなければならない。headerタグはサイトのヘッダー部分に、mainタグは主要コンテンツに、footerタグはフッターに使用する。ブロックテーマではグループブロックにmainのHTMLタグを割り当てることで対応できる」
具体的なHTMLタグ名と設定方法が明示されている
※Lyschik氏はRian Rietveld氏らと協力し、このギャップを埋めるドキュメント改善に取り組む意向を示している

Lyschik氏がWordCamp Europe 2026のセッションで伝えたかったのは、まさにこの点だ。要件文書の「行間」に埋もれた実装知識を言語化し、開発者が自信を持ってアクセシビリティに取り組めるようにすること。ドキュメント改善は現在進行形の課題だが、基本的なHTMLとCSSの知識があれば、大半の要件は想像よりはるかに簡単にクリアできる。

今日から実践できる3つの具体的な改善策

今日から実践できる3つの具体的な改善策

WP TavernのインタビューでLyschik氏は「ロー・ハンギング・フルーツ(手の届きやすい果実)」という表現を使った。大きな努力をしなくてもすぐに成果が出る、アクセシビリティ改善の第一歩が確かに存在する。以下は彼女が実際に推奨する3つの即効性のある施策だ。

画像の代替テキストを省略しない

最も基本的かつ効果が大きいのが代替テキスト(alt属性)の付与だ。WordPressのメディアライブラリには代替テキストを入力するフィールドが標準で用意されている。ブロックテーマなら画像ブロックを選択するだけで、サイドバーにaltテキスト入力欄が表示される。目の前にある入力欄を飛ばさずに埋めるだけの作業で、スクリーンリーダーユーザーやAIエージェントに画像の意味を伝えられる。

正しいHTMLタグの選択とスキップリンクの設定

ブロックテーマでは、HTMLタグの割り当てが驚くほど簡単になっている。コンテンツ全体をグループブロックで囲み、そのグループに「main」のHTMLタグを設定するだけで、WordPressがスキップリンク(Skip to Content)を自動生成する。このスキップリンクは、キーボード操作ユーザーが毎回ヘッダーメニューを通過せずに、直接本文へジャンプできる重要な導線だ。

クラシックテーマでは手動で実装する必要があったこの機能も、ブロックテーマなら数クリックで完了する。テンプレートパーツのheader/footerも適切に割り当てれば、Coreが自動で正しいHTML構造を出力してくれる。

本文中のリンクには下線を付ける

本文中のリンクテキストに下線を付けることは、CSS1行で実現できる変更だ。Lyschik氏は「text-decoration: underline; の1行を追加するだけ」と表現している。色だけに依存したリンク識別は、色覚特性のあるユーザーやコントラストが低下した環境で機能しなくなる。特にWCAG(Web Content Accessibility Guidelines)が要求するコントラスト比を満たす設計では、下線による補助表示が欠かせない。

アクセシビリティ非対応(Before)
詳しくはこちらの記事をご覧ください。
色の違いだけでリンクを識別。色覚特性によっては本文と区別できない
アクセシビリティ対応(After)
詳しくはこちらの記事をご覧ください。
下線付きで色に依存しないリンク識別が可能

Lyschik氏が強調するのは「最初から組み込む」ことの重要性だ。後から数百ページにわたってボタンのaria-labelを修正する作業を想像してみてほしい。彼女の同僚が実際に経験した「12箇所×2種類のボタン」修正は、事前に対応していれば5分で済んだはずの作業だった。あとから修正するコストは、最初に対応する手間と比べて指数関数的に増大する。

ブロックテーマが変えるアクセシビリティの常識

ブロックテーマが変えるアクセシビリティの常識

WordPressのテーマ開発はクラシックテーマからブロックテーマへと大きな転換点を迎えている。アクセシビリティの観点から見ると、この移行は単なるトレンドではなく、根本的な実装難易度の低下をもたらしている。

Coreが肩代わりするようになった処理群

ブロックテーマで最も大きな変化は、WordPress Core(コア)がアクセシビリティ対応の多くを自動処理するようになった点だ。Lyschik氏が具体的に挙げた例をいくつか紹介する。

Coreが自動処理する主な項目
スキップリンク mainタグ設定で自動生成
フォームラベル コメント・検索フォームで標準対応
HTML5セマンティクス テンプレートパーツ割り当てで自動出力
開発者が設定すべきこと  Coreが自動処理すること

特筆すべきは検索フォームとコメントフォームの扱いだ。クラシックテーマではフォームのラベル設定やエラー処理をテーマ開発者が実装する必要があったが、ブロックテーマではCoreがこれを完全に引き受けている。テーマ開発者はブロックを配置するだけで、自動的にアクセシブルなフォームが出力される。

既存テーマの構造をテンプレートとして再利用する

Lyschik氏が提案する効率的なアプローチは、アクセシビリティ対応済みのテーマからテンプレート構造をコピーすることだ。新しいテンプレートを作成する際、ゼロから設計するのではなく、すでに正しいHTML構造を持つ既存テンプレートを複製して色やレイアウトだけを変更する。これにより、header、main、footerのタグ割り当てが自動的に継承され、意図せずアクセシビリティを損なうリスクを回避できる。

この手法の前提として「どのテーマが正しくアクセシビリティ対応されているか」の知識が必要になるが、WordPress.orgテーマリポジトリで「accessibility-ready」タグを取得しているテーマ(現在約270テーマ、全体の1.5%程度)が信頼できる参照先となる。

AIエージェントが変えるアクセシビリティの優先順位

AIエージェントが変えるアクセシビリティの優先順位

インタビューの中でLyschik氏が特に強調したのが、AIエージェントの台頭がアクセシビリティの重要性を根本的に変えつつあるという洞察だ。この視点は、従来の「障がい者支援」という枠組みを超えて、アクセシビリティをビジネス上の競争力として再定義する可能性を秘めている。

AIエージェントは「見た目」ではなく「構造」を読む

Lyschik氏がAnne-Mieke Bovelett氏から共有されたという資料では、AIエージェントとスクリーンリーダーの動作原理が本質的に同じであることが指摘されている。AIエージェントはWebサイトを人間のように「視覚的」に理解するわけではない。HTMLの構造、適切なタグ、正確なラベル付けに依存して情報を取得し、操作を実行する。

AIエージェントがECサイトで買い物をする流れ
STEP 1 ユーザーがAIに「コーヒー豆を1kg購入して」と指示
STEP 2 AIエージェントがECサイトのHTML構造を解析
STEP 3 適切なラベル付きボタンを検出 → カートに追加 → チェックアウト
STEP 4 購入完了。不適切な構造のサイトではこの操作が途中で失敗する
アクセシビリティ非対応サイト:AIエージェントが操作に失敗し、販売機会を喪失
アクセシビリティ対応サイト:AIエージェントがスムーズに取引を完了

Googleが2026年6月に発表したドキュメントでも、AIエージェント向けのアクセシビリティに注力する方針が示されている。これは単なる技術的関心ではなく、ECサイトの将来像に直結する話だ。ユーザーが直接ブラウザを操作せず、AIエージェントに「コーヒー豆を購入して」と依頼する世界では、アクセシビリティ対応の有無が売上に直結する。

アクセシビリティは「コスト」ではなく「投資」になる

Lyschik氏が言及したAnne-Mieke Bovelett氏の事例では、ある企業がWebサイトのアクセシビリティ改善に取り組んだ結果、売上が実際に増加したという。この事例が示すのは、アクセシビリティ対応が単に「法的リスクの回避」や「道徳的義務」という枠を超えて、ビジネス成果に寄与するという事実だ。

AIエージェントの普及はこの傾向を加速させるだろう。適切に構造化されたHTML、明確なラベル付け、正しいフォーム処理を持つWebサイトは、AIエージェントによる自動操作の信頼性を高める。2025年以降、この「AIフレンドリー」な設計がECやサービスサイトの競争優位性を左右する可能性は高い。

最初から組み込むアクセシビリティの設計思想

最初から組み込むアクセシビリティの設計思想

Lyschik氏が一貫して訴えているのは「アクセシビリティは後付けの修正ではなく、設計段階から組み込むべきもの」という原則だ。彼女自身の経験と、同僚が直面した「24回のaria-label追加作業」のエピソードが、この主張を裏付けている。

ボタンのaria-label追加に見る「後付けの代償」

インタビューで紹介された実例がある。クライアントのアクセシビリティテストで、アイコンのみのボタン(電話アイコンなど)がスクリーンリーダーで機能しないと指摘された。アイコンだけでは「このボタンが何をするのか」を読み上げられないからだ。修正にはaria-label属性を追加するだけで済むが、問題はその数だった。12箇所×2種類のボタン、合計24回の手動修正が必要になった。

Lyschik氏はこの経験を「設計時にaria-labelを追加しておけば5分で終わっていた作業」と総括する。テーマやサイトの構築時にアクセシビリティを考慮していれば、後から数百ページにわたって同じ修正を繰り返す必要はなかったはずだ。

学際的な意識共有が不可欠

アクセシビリティは開発者だけの責任ではない。Lyschik氏は「interdisciplinary(学際的)」という言葉を使い、SEO担当者、コンテンツ制作者、デザイナーを含む全ての関係者が基本的な知識を持つべきだと指摘する。

各ロールに求められるアクセシビリティ知識
開発者 HTML構造、WAI-ARIA、フォームのラベル付け、スキップリンク実装
デザイナー コントラスト比(WCAG AA基準は4.5対1以上)、フォーカス表示の設計
コンテンツ制作者 正しい見出し階層(H1→H2→H3の順序)、代替テキストの記述
SEO担当者 アクセシビリティ対応が検索エンジン評価にも影響する構造的理解
見出しの階層を飛ばす(H2の次がH6になる)ことは、スクリーンリーダーユーザーに混乱を招き、SEO上もマイナス評価となる。正しい階層構造の維持は、全ロールに関わる共通ルールだ。

見出しタグ(H1〜H6)の正しい階層構造は、その典型的な例だ。H1の下にH2、その下にH3という順序を守ることは、スクリーンリーダーユーザーの文書理解を助けるだけでなく、検索エンジンのコンテンツ解析精度にも影響する。SEOとアクセシビリティは、しばしば同じ方向を向いている。

この記事のポイント

  • アクセシビリティ対応の難しさは「技術そのもの」ではなく「ドキュメントの分かりにくさ」に起因している
  • 画像の代替テキスト入力、正しいHTMLタグ割り当て、リンク下線付与は今日から着手できる即効性の高い施策だ
  • ブロックテーマではスキップリンクやフォームラベルなど、Coreがアクセシビリティ処理を大幅に肩代わりする
  • AIエージェントの普及により、アクセシビリティ対応はECサイトの売上やビジネス成果に直結する要素になりつつある
  • 設計段階からの組み込みが、後工程での膨大な修正作業を回避する最も効率的なアプローチだ
LiteSpeed Cacheでパージ成功してもキャッシュが残る原因と直し方

LiteSpeed Cacheでパージ成功してもキャッシュが残る原因と直し方

LiteSpeed Cacheで管理画面やWP-CLIから「Purge All(全キャッシュ削除)」を実行し成功メッセージが出ているにもかかわらず、公開ページがいつまでも古い内容のまま更新されない場合、プラグインのキャッシュ制御と実際に動作しているサーバーレベルのキャッシュ層との間に不整合が起きている可能性が高い。根本的には、LSCacheプラグインの設定最適化と、場合によってはプラグインを一時的に無効化しての強制リフレッシュが有効な解決策になる。

なぜ「全削除」に成功してもキャッシュが残り続けるのか

なぜ「全削除」に成功してもキャッシュが残り続けるのか

LiteSpeed Cacheのキャッシュ構造は多層的で、プラグインの管理画面と実際のキャッシュストレージが常に一対一で対応しているとは限らない。管理画面で「Purge All」を実行すると、プラグインは内部的にパージタグを生成し、LSWS(LiteSpeed Web Server)に対してキャッシュ削除の指示を出す。ここで成功メッセージが返ってくるのは、あくまで「指示が出せた」という意味であり、ディスクやメモリ上の実ファイルが完全に消えたことの保証にはならない。

まず試すべきプラグイン側の設定見直し

まず試すべきプラグイン側の設定見直し
修正前の状態
パージを実行しても、レスポンスヘッダーに x-litespeed-cache:hit が返ってくる。
x-litespeed-cache: hit
x-litespeed-cache-control: no-cache
修正後の状態
キャッシュヘッダーが miss になり、サーバーが動的生成に切り替わった。
x-litespeed-cache: miss
キャッシュヒットして古い内容が表示される  キャッシュミスで最新の内容が表示される

キャッシュ有効期限とブラウザキャッシュ設定の確認

まず、LSCacheプラグインの「キャッシュ」タブでTTL(Time To Live、キャッシュの有効期限)が極端に長く設定されていないか確認する。デフォルトは604800秒(1週間)だが、公開ページの更新頻度が高いサイトでは3600秒(1時間)程度に短くすることで、パージ操作の効果が出やすくなる。また「ブラウザキャッシュ」タブで、ブラウザ側のキャッシュ保持期間が長すぎると、サーバーのパージ後もユーザーのローカルキャッシュが優先されて古い表示になる。短期間に設定するか、問題切分け中は一時的に無効化(オフ)にする。

オブジェクトキャッシュとRedisの影響を見極める

RedisやMemcachedといった外部オブジェクトキャッシュを使っている環境では、LSCacheがページキャッシュを削除しても、データベースクエリの結果がオブジェクトキャッシュに残り、結果として同じ古い情報でページが再生成される。Redisを使っているなら、管理画面の「LiteSpeed Cache」→「オブジェクトキャッシュ」でステータスを確認し、「Purge Object Cache」を手動で実行する。それでも直らない場合は、問題の切分けとしてredis-cacheやRedis Object Cacheプラグイン自体を一度無効化し、LSCacheだけの状態でパージを試す。

サーバーレベルで強制的にキャッシュを削除する手順

プラグインの操作で改善しない場合、LiteSpeed Web Serverが保持しているキャッシュをOSレベルで手動削除する。特に、LSCacheプラグインが生成したキャッシュ情報と、LSWSの内部キャッシュマップがずれてしまった場合に有効だ。

サーバーレベルの強制キャッシュクリア手順
STEP 1 SSHでサーバーにログインし、LSWSのキャッシュディレクトリを特定する
STEP 2 LSWSを完全停止し、スワップファイルとキャッシュファイルをすべて削除する
STEP 3 LSWSを起動し、LSCacheプラグインの「Purge All」を再度実行する
STEP 4 ブラウザのシークレットモードで動作を確認する

ディスクキャッシュの格納場所と削除コマンド

SSHでサーバーに接続し、まずLSWSのキャッシュパスを特定する。デフォルトでは /tmp/lshttpd/ または /usr/local/lsws/cachedata/ 以下にキャッシュが格納されている。LSCacheプラグインの「CDN/キャッシュ設定」にある「キャッシュルートパス」を確認すると確実だ。

次に、LSWSを完全に停止する。systemctl restart lsws や管理画面からのグレースフルリスタートではプロセスがクリアされず、内部のキャッシュマップが生き残る。以下の手順で、キャッシュディレクトリを物理的に空にしてから再起動する。

# LSWSを完全停止
/usr/local/lsws/bin/lswsctrl stop

# キャッシュディレクトリとスワップを完全に削除
rm -rf /tmp/lshttpd/swap/*
rm -rf /tmp/lshttpd/cache/*

# LSWSを起動
/usr/local/lsws/bin/lswsctrl start

起動後、WordPress管理画面からLSCacheの「Purge All」を実行し、ブラウザのシークレットウィンドウ(または開発者ツールでキャッシュを無効化した状態)で動作を確認する。x-litespeed-cache ヘッダーが miss になっていれば成功だ。

どうしても治らない場合の一時的切り分け

上記の手順を実行しても、ベアURL(パラメータなしの通常アクセス)だけが古いキャッシュを返し続ける場合、LSCacheプラグイン自体がリクエスト段階でキャッシュの再取得を意図せず阻害している可能性が高い。この状況は、特定の条件下でLSCacheプラグインがブラウザやリバースプロキシに強力なCache-Controlヘッダーを設定し、サーバー側でミスになってもCDNやブラウザが古いデータを返し続けるケースだ。

最終的な切分けとして、LSCacheプラグインを5分〜10分間だけ完全に無効化する。無効化後に即座にキャッシュが更新されるなら、LSCacheプラグインの内部ロジックまたは設定が主因であると特定できる。プラグインを再有効化した後、デフォルト設定へのリセットや、問題を再現させた設定項目を一つずつ絞り込む。

よくある質問

クエリ文字列を付けるとキャッシュがバイパスされる理由は?

LiteSpeed Cacheは、URLにパラメータ(?=xxx)が付与されたリクエストを、デフォルトでキャッシュ対象外とみなす。そのためベアURLでは x-litespeed-cache:hit で古いキャッシュが返っているのに、/?v=2 のような文字列を付けると miss となり、動的に生成された最新ページが表示される。

サーバーを再起動できない共有サーバーではどう対処すればよいか

多くの共用サーバーではSSHやLSWSの制御権限が制限されている。この場合、LSCacheプラグインの「ツールボックス」→「Purge All」を実行し、即座にプラグインを無効化する。その後「LiteSpeed Cache」を再有効化し、デフォルト設定へのリセットを試す。サーバー側のキャッシュディレクトリに触れない環境では、この手順が最も確実な強制リセットになる。

パージ後のキャッシュ再生成を防ぎたい場合の設定は?

公開前のプレビューや検証でキャッシュを完全に止めたい場合、LSCacheプラグインの「キャッシュ」→「キャッシュを有効化」のトグルを一時的にオフにする方法が最も確実だ。ただしこの設定はサイト全体のパフォーマンスに影響するため、検証後は必ずオンに戻し、特定のページだけ除外したい場合は「URI 除外」機能を使う。

この記事のポイント

  • LSCacheのパージ成功表示が実キャッシュ削除の保証にはならない
  • サーバーレベルでディスクキャッシュを直接削除すると治るケースが多い
  • Redisなどの外部オブジェクトキャッシュも同時に削除する必要がある
  • クエリ文字列でバイパスされる現象はLSCacheの正常動作だが、ベアURLが古いのは異常
  • どうしても治らなければ、プラグインの一時無効化で原因をLSCacheに特定できる
WordPress wp2shell攻撃後の完全クリーンアップと再発防止手順

WordPress wp2shell攻撃後の完全クリーンアップと再発防止手順

WordPress が REST Batch API を悪用した wp2shell 攻撃を受け、Web シェルを仕込まれたり偽の管理者を作られたりした場合は、WordPress 本体の更新だけでなくファイルとデータベースの完全な掃除が必要になる。攻撃者は認証なしでコードを実行できるため、必ず FTP/SSH と SQL の両面から対処する。

この攻撃は SQL インジェクションで PHP ファイルをサーバーに直接書き込み、データベース内にも偽のメニュー項目や changeset を残す。パッチを当てただけではバックドアが生き残り、再侵入される危険がある。

Before 攻撃直後、WordPress だけ更新した状態
✗ /wp-content/cache/ に不明な PHP ファイルが残る ✗ データベースに偽の customize_changeset が残る ✗ 攻撃者の管理者アカウントが有効なまま ✗ 全員のセッションが生きている
After 完全クリーンアップ後の状態
✓ キャッシュ内の Web シェルを全削除 ✓ DB から偽データを削除し FILE 権限を剥奪 ✓ 全員のパスワード変更とセッション強制切断 ✓ DISALLOW_FILE_MODS でファイル改変を遮断

上図は攻撃直後と完全クリーンアップ後の違いだ。この記事ではファイル・データベース・ユーザー管理の3層で掃除し、再発を防ぐまでの手順を詳しく解説する。

wp2shell 攻撃で何が起きているのか

wp2shell 攻撃で何が起きているのか

WordPress の REST Batch API は複数の REST API リクエストをまとめて送信できる機能だ。この脆弱性を突かれると、認証なしで SQL インジェクションを起こし、INTO OUTFILE 命令で PHP ファイルをサーバーに書き込める。書き込まれた PHP ファイルは Web シェルとして機能し、攻撃者が自由にコマンドを実行できるようになる。

さらに攻撃者はデータベースに直接アクセスできるため、パスワードのハッシュを盗んだり、自分自身に管理者権限を付与したりすることが可能だ。偽のプラグインを /wp-content/mu-plugins/ に仕込んで恒久的なバックドアにする手口も確認されている。

被害にあったサイトを完全に掃除する手順

被害にあったサイトを完全に掃除する手順

掃除は「ファイルの掃除」「ユーザーとセッションの掃除」「データベースの掃除」の3段階で進める。始める前に必ずファイルとデータベースの完全バックアップを取る。

ファイルシステムから Web シェルと偽プラグインを削除する

FTP クライアントや SSH でサーバーに接続し、まず /wp-content/cache/ ディレクトリを開く。キャッシュ系のディレクトリ(/cache//wpo-cache/ など)に不自然な英数字の羅列(e042u9xy9ra1.php のようなファイル名)があれば、それが Web シェルだ。すべて削除する。

次に /wp-content/mu-plugins/ を確認する。Must-Use プラグインは自動的に有効化されるため、攻撃者が好んで使う場所だ。身に覚えのないファイル(galex_patch.php など)があれば削除する。通常の /wp-content/plugins/ にも見慣れないプラグインが追加されていないか調べる。

STEP 1 FTP/SSH でサーバーに接続
STEP 2 /wp-content/cache/ 内の怪しい PHP ファイルを削除
STEP 3 /wp-content/mu-plugins/ の身に覚えのないファイルを削除
STEP 4 WordPress 本体を 6.9.5 または 7.0.2 以上に更新
STEP 5 wp-config.php に DISALLOW_FILE_EDIT / DISALLOW_FILE_MODS を追加

ファイルの掃除が終わったら、WordPress 本体をパッチ適用済みバージョン(6.9.5 または 7.0.2 以上)に更新する。この更新で Batch API の脆弱性自体が塞がる。

wp-config.php にファイル改変防止の定数を追加する

将来の攻撃に備え、管理画面からのファイル編集とプラグインのインストールを無効化する。FTP または SSH で wp-config.php を開き、以下の2行を追記する。

define( 'DISALLOW_FILE_EDIT', true );
define( 'DISALLOW_FILE_MODS', true );

DISALLOW_FILE_EDIT は管理画面の「テーマエディター」「プラグインエディター」を無効化する。DISALLOW_FILE_MODS は管理画面からのプラグイン追加や更新をブロックする。すでに侵入された後の対策ではなく、掃除が完了した後に再発を防ぐ設定だ。

ユーザーとパスワード、セッションの掃除

ユーザーとパスワード、セッションの掃除

攻撃者はデータベースに直接アクセスしていたため、パスワードのハッシュを抜き取った可能性が高い。管理者を含む全ユーザーのパスワードを変更する。WordPress 管理画面だけでなく、MySQL データベース自体のパスワードも変更し、wp-config.phpDB_PASSWORD を新しいものに書き換える。

パスワード変更だけでは不十分だ。攻撃者がまだログインしたままかもしれない。全セッションを強制的に切断するには、Salt Keys(ソルトキー)を再生成して wp-config.php に上書きする。WordPress 公式の Salt Keys ジェネレーターで新しいキーセットを取得し、既存のキーと置き換えれば全デバイスのログイン状態が即座に無効になる。

最後に、管理画面の「ユーザー」一覧を開き、見覚えのない管理者アカウントが作られていないか確認する。もし存在すれば即座に削除する。

データベースに残った偽のレコードを削除する

データベースに残った偽のレコードを削除する

wp2shell 攻撃は、WordPress のセキュリティフィルターをすり抜けるために、データベース上に偽のナビゲーションメニュー項目と changeset(カスタマイザーの変更履歴)を一時的に生成する。これらのレコードは post_date2020-01-01 00:00:00 に固定されており、URL ペイロードに example.invalid を含むという特徴がある。

以下の SQL を phpMyAdmin などで実行する。なお、接頭辞(プレフィックス)がデフォルトの wp_ ではない場合は、コード内の wp_ を実際のプレフィックス(例: wp2_mysite_)に置き換える必要がある。

偽の changeset を削除する

DELETE FROM wp_posts WHERE post_type = 'customize_changeset' AND post_date = '2020-01-01 00:00:00';

偽のナビゲーションメニュー項目を削除する

DELETE FROM wp_posts WHERE post_type = 'nav_menu_item' AND post_date = '2020-01-01 00:00:00';

悪意のあるメタデータを削除する

DELETE FROM wp_postmeta WHERE meta_value LIKE '%example.invalid%';

これらのクエリでデータベース上の痕跡を一掃できる。実行前には必ずデータベースのバックアップを取る。

データベースユーザーから FILE 権限を剥奪する

データベースユーザーから FILE 権限を剥奪する

今回の攻撃は SQL インジェクション経由で INTO OUTFILE 命令を使い、データベースからディスクにファイルを書き込んでいた。WordPress の通常動作に FILE 権限はまったく不要だ。この権限を WordPress のデータベースユーザーから剥奪しておけば、同種の攻撃が再び成功する可能性を大きく下げられる。

データベースに root 権限でログインし、以下の SQL を実行する。'wp_user_name'@'localhost' は実際の WordPress 用データベースユーザー名に置き換える。

REVOKE FILE ON *.* FROM 'wp_user_name'@'localhost';
FLUSH PRIVILEGES;

REVOKE FILE は FILE 権限を取り消し、FLUSH PRIVILEGES は変更を即時反映する。これでデータベースからファイルシステムへの書き込み経路が遮断される。

Before FILE 権限あり
攻撃者 → SQL インジェクション → INTO OUTFILE → ディスクに PHP ファイル書き込み
After FILE 権限なし
攻撃者 → SQL インジェクション → INTO OUTFILE権限エラーで書き込み失敗
権限あり(攻撃経路あり)  権限剥奪後(経路遮断)

よくある質問

WordPress を更新しただけで掃除は不要か

更新だけでは不十分だ。攻撃者はすでに Web シェルをサーバーに書き込んでおり、そのファイルは更新では削除されない。データベース内の偽レコードや不正な管理者アカウントも残ったままになる。必ずファイルとデータベースの両方を掃除する。

DISALLOW_FILE_MODS を設定すると管理画面からプラグインを追加できなくなるのか

そのとおりだ。DISALLOW_FILE_MODStrue にすると、管理画面からのプラグインのインストール・更新・削除、テーマのインストール・更新がすべてブロックされる。必要な更新は FTP や SSH 経由で手動で行う運用になるが、攻撃者が管理画面からファイルを改変する経路を完全に塞げる。

SQL クエリのプレフィックス wp_ を変更し忘れるとどうなるか

テーブルが存在しないというエラーが出るだけで、データが破壊されることはない。それでも、間違ったテーブルを操作しないよう実行前に必ず実際のプレフィックスを確認する。多くのレンタルサーバーではインストール時に自動生成された固有のプレフィックスが使われている。

FILE 権限を剥奪すると WordPress の動作に影響は出るか

WordPress の通常動作に FILE 権限は一切使われない。記事の投稿やプラグインの動作、データベースの読み書きに影響は出ないため、安全に剥奪できる。

この記事のポイント

  • WordPress 本体の更新だけではバックドアとデータベースの痕跡が残る
  • /wp-content/cache/ と /wp-content/mu-plugins/ の不審な PHP ファイルを削除する
  • 全ユーザーのパスワード変更と Salt Keys 再生成でセッションを強制切断する
  • データベースから post_date が 2020-01-01 の偽 changeset とメニュー項目を削除する
  • WordPress の DB ユーザーから FILE 権限を剥奪し INTO OUTFILE 経路を塞ぐ
WooCommerce.comがプラグイン読み込みを最適化、応答時間を最大400ms短縮

WooCommerce.comがプラグイン読み込みを最適化、応答時間を最大400ms短縮

WooCommerce.comが大規模WordPressサイトのパフォーマンスを大幅に改善した手法が公開された。特定のリクエストで不要なプラグインを読み込まない「選択的プラグイン読み込み」である。この手法により、WooCommerce.comの内部APIエンドポイントではメモリ使用量が50%以上削減され、主要エンドポイントの応答時間が最大400ミリ秒以上短縮されたという。

WordPressはリクエストのたびにすべての有効化プラグインを読み込む仕組みだ。大多数のサイトでは問題にならないが、WooCommerce.comのように100を超えるプラグインが稼働する大規模サイトでは無視できないオーバーヘッドになる。今回の事例は、大規模WordPressサイトのパフォーマンスチューニングに新たな選択肢を示すものだ。

プラグインの一括読み込みは大規模サイトの足かせになる

プラグインの一括読み込みは大規模サイトの足かせになる

WordPressのプラグインモデルはシンプルだ。有効化されているプラグインは、フロントエンドの表示でも管理画面の操作でも、REST APIの呼び出しでも、すべてのリクエストで例外なく読み込まれる。ほとんどのサイトにとってこれは合理的な設計である。挙動が予測しやすく、プラグイン同士の依存関係を意識せずに組み合わせられる。

しかしWooCommerce.comのように、マーケットプレイス、決済、アカウント管理、API、チェックアウト、パートナー向けワークフロー、検索連携、トラッキング、そして運用コードが複雑に絡み合う大規模アプリケーションでは、事情が異なる。100個以上のプラグインのうち、特定のリクエストで実際に必要なのはごく一部であることが多いのだ。

WooCommerce.comの開発者ブログで紹介された実例を見てみよう。商品の検索・表示ページは、マーケットプレイスへの出品ツールを必要としない。キャッシュされた内部APIは、チェックアウト処理と同じプラグイン群を必要としない。公開ドキュメントの表示に注文番号の採番ロジックは不要だ。にもかかわらず、これらすべてのリクエストが同じプラグインセットを読み込んでいる。

各プラグインは読み込み時にフックの登録、サービスの初期化、オプションの読み出し、翻訳ファイルのロード、カスタム投稿タイプの定義、RESTルートの追加、アセットのキューイング、互換性コードの実行などを行う。1つひとつは小さなコストでも、成熟したWooCommerceアプリケーションで積み重なると無視できない負荷になる。

ページキャッシュやエッジキャッシュはこの問題をある程度隠すが、根本的な解決にはならない。キャッシュミスは依然として発生する。APIリクエストは動的なものが多い。ログイン状態のリクエストはキャッシュをバイパスする。トラフィックが急増するタイミングで、運用系のエンドポイントが高いレイテンシに悩まされることもある。大規模WordPressサイトにとって、ブートストラップ処理の削減は確かなパフォーマンス向上手段だ。

選択的プラグイン読み込みの基本的な仕組み

選択的プラグイン読み込みの基本的な仕組み

WordPressは有効化プラグインの一覧を active_plugins オプションに保持している。ブートストラップ時にこのオプションを読み取り、リストにある各プラグインのメインファイルを順に読み込んでいく。

ここに介入する仕組みがオプションフィルターだ。pre_option_active_plugins または option_active_plugins フィルターを使えば、WordPressが実際にプラグインを読み込む前にリストを書き換えられる。重要なのは、このフィルターを通常のプラグインより先に実行される mu-plugin(Must-Use Plugin)に配置することだ。

add_filter(
    'option_active_plugins',
    function ( array $plugins ): array {
        if ( ! should_limit_plugins_for_this_request() ) {
            return $plugins;
        }
        return array_values(
            array_diff(
                $plugins,
                plugins_to_skip_for_this_request()
            )
        );
    }
);

このコードの要点は3つある。どのリクエストでフィルターを適用するか、そのリクエストにとって安全に除外できるプラグインはどれか、そしてプラグインの一部を読み込まなかったことでサイト全体の状態が破損しないか、という点だ。

WooCommerce.comでは、mu-pluginでルートルールを早期に登録し、リクエストURIを完全一致、前方一致、または正規表現で照合する。ルールがマッチすると、あらかじめ定義されたプラグインを読み込み対象から外す仕組みだ。

従来のリクエスト処理(Before)
⚙️ 決済ゲートウェイ
⚙️ 税金計算プラグイン
📦 マーケットプレイス出品ツール
📝 ブログ用SEOプラグイン
✅ 注文管理プラグイン
全プラグインを読み込み → メモリ消費大
高速化後のリクエスト処理(After)
⚙️ 決済ゲートウェイ
⚙️ 税金計算プラグイン
📦 マーケットプレイス出品ツール
📝 ブログ用SEOプラグイン
✅ 注文管理プラグイン
必要なプラグインのみ読み込み → メモリ消費50%以上の削減が可能
テキスト+灰色背景 = 除外(ロードされない)  = APIが実際に必要とするプラグイン

この仕組みは、APIエンドポイントやブログ記事の表示など「ほとんどのプラグインが不要なリクエスト」で高い効果を発揮する。

ルール設計と安全性のトレードオフ

ルール設計と安全性のトレードオフ

許可リストと除外リスト

選択的プラグイン読み込みの設計で最初に直面するのが、読み込むプラグインを決める方式だ。大きく分けて許可リスト方式と除外リスト方式がある。

許可リスト方式は、そのルートに絶対必要なプラグインだけをゼロからリストアップする。理論上は最も効果が高いが、大規模サイトでは脆さが問題になる。テーマ関数が別プラグインのクラスに依存しているケース、RESTハンドラが間接的に他のプラグインのヘルパー関数を呼んでいるケース、キャッシュミス時にのみ実行されるコードパスなど、暗黙の依存関係を見落とすとルートが壊れる。見落としはテストをすり抜けやすい。

除外リスト方式は、通常のアクティブプラグインリストから「このルートでは確実に不要」とわかっているものだけを取り除く。理想的な最小化にはならないが、安全性が段違いに高い。管理画面専用ツール、決済ゲートウェイ、メール配信処理など、明らかに関係ないグループをまとめて外すので、予期せぬ依存による障害が起こりにくい。

WooCommerce.comのチームは後者を主軸に採用している。開発者ブログの言葉を借りれば「各ルールをコードレビューの差分として確認でき、どのプラグインを残したか、なぜ外したかをコメントで説明できる」状態が、運用上の安心感をもたらすという。

除外が危険なリクエスト

すべてのリクエストが選択的読み込みの対象になるわけではない。WooCommerce.comでは以下のリクエストタイプを一律で除外対象から外している。

  • wc-ajax
  • wc-api
  • rest_route(広範なクエリ文字列エントリポイント)
  • クエリパラメータを含むダウンロードリクエスト

これらは動的に多様なコードパスに分岐する可能性があり、副作用の範囲を予測しにくいためだ。チェックアウト、カート、アカウント、管理画面、cron、Webhook、決済コールバック、ダウンロードといった「金銭・顧客データ・認証・権利確認・メール送信・サードパーティ連携」に触れるリクエストには、特に慎重な扱いが求められる。

依存関係の発見が最大の難所

動的なWordPressアプリケーションでは、あるルートが実際にどのプラグインに依存しているかを静的に解析する方法は存在しない。WooCommerce.comのチームは、ルートのコードを読み込み、明白な依存関係を追跡し、手動でリクエストフローを検証し、URLマッチングとエンドポイント動作のテストを書き、段階的にロールアウトしてエラーログとパフォーマンスデータを監視する、という実践的なアプローチを取っている。

注意すべきは、問題が特定の条件下でのみ表面化することだ。特定の商品ページ、特定のロケール、特定のリクエストパラメータ、キャッシュミス時、ログイン状態など、組み合わせは膨大になる。プラグインの読み込み不足によるエラーは再現条件が狭く、発見が遅れやすい。

WooCommerce.comで得られた具体的な効果

WooCommerce.comで得られた具体的な効果

WooCommerce.comの開発者ブログでは、いくつかのエンドポイントで測定された具体的な数値が公開されている。

  • 必要なプラグインのみを読み込んだリクエストでは、メモリ使用量が50%以上削減された
  • 接続ストアが所有する有料サブスクリプション情報を返すエンドポイントは、約800msから約475msに短縮
  • ストアにインストールされたプラグインの利用可能アップデートを通知するエンドポイントは、約880msから約550msに短縮
  • WooCommerceオンボーディングフローで毎回呼ばれるエンドポイントも、同様の大幅改善
  • 商品ページに対して決済ゲートウェイと無関係な拡張機能を除外した初期テストでは、ページ生成時間が約10%改善

これらの改善は、高トラフィックで責務が限定されたエンドポイントや、大規模なプラグイン構成を持つ匿名ユーザー向けページで特に顕著だった。逆に、ほぼすべてのプラグインが関与しうる動的なステートフルフローでは、相対的な効果は小さくなる。

監視すべき指標は、レスポンスタイム、PHPメモリ使用量、データベースクエリ数、高コストなプラグインのブートストラップ処理、デプロイ後のエラー率、予期しないリライトルールの変更、キャッシュミス時のルート固有の致命的エラーなど、多岐にわたる。

この手法が適するサイトと適さないサイト

この手法が適するサイトと適さないサイト

この手法が効果を発揮するのは、以下の条件が揃っているサイトだ。

✅ 導入に向いているサイト
• アクティブプラグインが多い(数十個以上)
• トラフィックが高く、ブートストラップコストが無視できない
• 特定のルートの責務が限定されている
• エンジニアリングチームがデプロイと監視を担当できる
• テストの作成と維持が可能
• 改善効果を測定できる
• 問題が起きたときに素早くロールバックできる
❌ 導入が不向きなサイト
• 小規模サイト
• プラグイン数がすでに少ない
• ルートが動的でステートフル
• ステージング環境や本番監視がない
• 非エンジニアが依存関係ルールを維持する必要がある
• 依存関係の発見作業を継続的に行う余裕がない
適性あり  適性なし

繰り返しになるが、これは最初に手をつけるべき最適化ではない。キャッシュ戦略の見直し、クエリパフォーマンスの改善、アセット読み込みの最適化、オブジェクトキャッシュの導入、明らかに不要なプラグインの整理といった基本的な施策を先に済ませてから検討するのが現実的な順序だ。

STEP 1 ページキャッシュ・CDNの導入
STEP 2 データベースクエリの最適化
STEP 3 オブジェクトキャッシュ・アセット最適化
STEP 4 選択的プラグイン読み込みの検討
選択的プラグイン読み込みは、基本的な高速化施策を実施した後の追加施策として位置づける

この記事のポイント

  • 特定のリクエストで不要なプラグインを読み飛ばす「選択的プラグイン読み込み」により、WooCommerce.comの大規模サイトでメモリ使用量50%以上の削減と大幅な応答時間短縮が達成された
  • 仕組みは option_active_plugins フィルターとmu-pluginの組み合わせで実装でき、技術的なハードル自体は高くない
  • 安全性を確保するには「許可リスト」より「除外リスト」方式が現実的であり、ルールをコードとして管理しテストと監視を徹底することが不可欠
  • この手法は大規模サイト向けの「最後の一手」であり、キャッシュやクエリ最適化といった基本的な施策を先に実施すべき
WooCommerceバリエーション商品のカート追加で重大エラーが出る時の修正方法

WooCommerceバリエーション商品のカート追加で重大エラーが出る時の修正方法

WooCommerce のブロックベースカートでバリエーション商品を追加した際に「woo-min-max-quantity-step-control-single」プラグインが原因で「このサイトで重大なエラーが発生しました」と表示される場合、原因はプラグインが商品オブジェクトの取得失敗を考慮していないことだ。該当ファイルの PHP コードに数行の修正を加えればエラーを回避できる。

なぜブロックカートでバリエーション商品だけエラーになるのか

エラーログを確認すると、問題はプラグインの min-max-controller.php 872行目付近で発生している。この行では wc_get_product() で商品情報を取得した直後に ->get_id() を呼んでいるが、wc_get_product() は対象の商品が見つからなかった場合に false を返す。ブロックベースのカート(Store API)経由でバリエーション商品が追加されると、この関数がまれに false を返す状況が発生する。プラグインのコードはその可能性をまったく想定しておらず、結果として「Call to a member function get_id() on false」という致命的な PHP エラーに直結している。

エラーを根本解決するためのコード修正手順

エラーを根本解決するためのコード修正手順
STEP 1 FTP や管理画面から該当ファイルを探す
STEP 2 872 行目付近のコードを確認し false チェックを追加する
STEP 3 修正したファイルをサーバーにアップロードする

該当ファイルを開き 872 行目周辺を特定する

FTP クライアント、またはレンタルサーバーのファイルマネージャーで、以下のパスにあるファイルを開く。WordPress 管理画面の「プラグイン」→「プラグインファイルエディター」から編集するのはできるだけ避ける。エディター画面で編集ミスをするとサイト全体が停止するリスクがあるためだ。

/wp-content/plugins/woo-min-max-quantity-step-control-single/includes/min-max-controller.php

使用しているエディタで行番号表示を有効にし、872 行目付近まで移動する。エラーログに表示されているとおり、->get_id() を直接呼んでいる箇所を探そう。

修正前と修正後のコードを比較する

修正前(エラー) Before
$product = wc_get_product( $product_id );
$min = $product->get_id();
修正後(安全) After
$product = wc_get_product( $product_id );
if ( ! $product || ! is_a( $product, ‘WC_Product’ ) ) {
    return;
}
$min = $product->get_id();
エラー状態  修正後

PHP コード修正の内容と意味

修正の核心は、$product が有効なオブジェクトかどうかを事前に検証することだ。

! $productwc_get_product()false を返した場合を検出する。is_a( $product, 'WC_Product' ) は、取得できたとしてもそれが WooCommerce の正規の商品オブジェクトであることを確認する。どちらかの条件を満たさなければ return で処理を中断し、以降の ->get_id() が呼ばれないようにする仕組みだ。

このガード節を入れることで、ブロックカート経由でどういった商品情報が渡ってきても、致命的なエラーにはならなくなる。

修正してもエラーが直らない場合の確認点

まれに、修正だけでは根本解決しないケースがある。以下の点も確認してみてほしい。

WooCommerce データベースのクリーンアップを実行する

「WooCommerce」→「ステータス」→「ツール」タブに移動し、「WooCommerce トランジェントをクリア」と「期限切れのトランジェントを削除」を実行する。キャッシュが原因で商品情報が正しく取得できていない場合に効果がある。

最小最大数量プラグインのバージョンと WooCommerce のバージョンを確認する

プラグインが最新の WooCommerce や Store API に対応していない可能性がある。プラグインの公式ページで対応バージョンを確認し、場合によっては別の数量制御プラグインを検討する必要がある。修正を加えても根本的な設計の問題で他の不具合が発生する場合は、開発者に修正リクエストを送りつつ、一時的に従来のショートコードカート([woocommerce_cart])へ切り戻すことも視野に入れる。

よくある質問

この問題はブロックカートだけに発生するのか

主にブロックベースのカートとチェックアウト(Store API)で発生する。従来のショートコードカートではエラーが出ないことも多いが、プラグインコードに潜在的な問題があるため、修正しておく方が安全だ。

プラグインがアップデートされたら修正は消えるか

消える。今回の修正はプラグインのコアファイルを直接編集しているため、プラグインにアップデートが配信されると編集内容は上書きされる。アップデート後に同様のエラーが再発した場合は、再び同じ手順で修正するか、開発者が修正を取り込むまでアップデートを控える必要がある。

子テーマの functions.php でこのエラーを回避できるか

難しい。今回のエラーはプラグイン内部の特定の行で発生しており、かつ商品取得のロジック周辺にフックが用意されていない限り、テーマファイル側から割り込んで防御することはできない。直接ファイルを編集する今回の方法が最も確実な対処法になる。

修正中にサイトが壊れてしまった場合の直し方は

FTP でサーバーにアクセスし、修正前にバックアップしておいた min-max-controller.php を上書きアップロードする。バックアップがない場合は、プラグインを一度削除して再インストールするのが早い。ただし、プラグインの設定内容はあらかじめメモしておく必要がある。

この記事のポイント

  • エラー原因はプラグイン内での get_id() 呼び出し前に false チェックがないこと
  • 修正対象は min-max-controller.php の 872 行目付近
  • wc_get_product()false を返す状況を考慮したガード節を追加する
  • ブロックカート(Store API)利用時に特に発生しやすい PHP エラーへの対処法
  • 直接ファイルを修正するため、プラグインのアップデートで編集が上書きされる点に注意
WordPressカスタムHTMLブロックが編集画面で消える原因と直し方

WordPressカスタムHTMLブロックが編集画面で消える原因と直し方

WordPress のブロックエディターでカスタム HTML ブロックを保存後に編集画面を開き直すと、ブロックの中身がまるごと消えて空欄に見える現象は、Gutenberg の React 側パーサーが HTML 構造を正しく解析できずに描画を放棄しているのが主な原因だ。script タグや閉じタグのない要素、複雑なインラインスタイルなどが引き金になる。

なぜカスタムHTMLブロックが編集画面で空になるのか

なぜカスタムHTMLブロックが編集画面で空になるのか

フロントエンドでは正しく表示されるのに、管理画面のブロックエディターだけで中身が空になるのは、データそのものはデータベースに保存されている証拠だ。コードエディターモードに切り替えれば、貼り付けた HTML が消えずに残っているのが確認できる。この現象が起こるのは、ブロックエディターがビジュアルモードでブロック内容を再表示するときに、DOM パーサーが特定の HTML を「壊れた構造」と判断してレンダリングを飛ばしてしまうからだ。

具体的には、Gutenberg が内部的に使用している React の DOM レンダリング機構が、管理画面の安全な枠組みの中で処理できないタグや構文に遭遇すると、そのブロック全体を安全のためにスキップする。結果として視覚的には空のブロックに見えるが、データベースの post_content には元のコードがそのまま格納されている。

カスタムHTMLブロックの中身が消える原因を特定する手順

カスタムHTMLブロックの中身が消える原因を特定する手順

まず、どの部分が問題を引き起こしているのかを切り分ける。以下の手順で原因箇所を絞り込む。

ブロックエディターのコードエディターモードで現状を確認する

編集画面右上の「︙」メニューから「コードエディター」を選択すると、ビジュアル表示ではなく生の HTML 構造が表示される。カスタムHTMLブロック内のコードがここで確認できれば、データは失われていない。このとき、Markdown 記法でブロック境界を示す <!-- wp:html --><!-- /wp:html --> のコメント行の内側にコードが残っているかを確かめる。

HTMLを最小単位に分解して原因のタグを特定する

ブロックに貼り付けた HTML を、1つのタグ単位で分割してカスタムHTMLブロックに1つずつ入れ直す。たとえば script タグ、style タグ、空要素(<div></div> など)、インラインイベントハンドラ(onclick など)をそれぞれ別のブロックに分けて保存し、再読み込み時に消えるかどうかをテストする。問題を起こすタグが特定できれば、その部分だけ別の実装方法に切り替えられる。

プラグイン競合の可能性を調べる

特定のプラグインがブロックエディターの動作に干渉して、カスタムHTMLブロックの描画を阻害しているケースもある。特にエディター拡張系のプラグインや、セキュリティ目的でスクリプトをフィルタリングするプラグインが入っている場合は、すべてのプラグインを一度無効化して標準テーマに切り替え、問題が再現するか確認する。プラグインが原因であれば、1つずつ再有効化して犯人を特定する。

カスタムHTMLブロックを正常に表示させる具体的な修正手順

カスタムHTMLブロックを正常に表示させる具体的な修正手順

原因が特定できたら、次は実際にブロックを正常に表示させる。script タグや複雑な HTML 構造が原因だった場合、以下のアプローチで回避できる。

STEP 1 原因となっているタグを特定する(script タグなど)
STEP 2 script タグはカスタムHTMLブロックの外に出す
STEP 3 複雑なHTMLはACFのフィールドやウィジェットで管理する
STEP 4 コードエディターモードで編集する運用に切り替える

script タグや iframe はカスタムHTMLブロックではなく適切な場所に配置する

Gutenberg のカスタムHTMLブロックは、セキュリティ上の理由から script タグの実行を制限している。また、ビジュアルエディターでの再レンダリング時に script タグを含むブロックを空として扱う挙動が確認されている。JavaScript を埋め込みたい場合は、カスタムHTMLブロックではなく、functions.php に wp_enqueue_script で登録するか、専用のコード埋め込みプラグインを使う。iframe も同様に、エディターのプレビューで空白になることがあるため、フロントエンドのみで描画される仕組みを検討する。

閉じタグのない空要素を見直す

カスタムHTMLブロックに <div></div> のような中身のない空タグや、閉じタグを省略した要素が含まれていると、Gutenberg のブロックパーサーが構造を正しく解釈できずにブロック全体をスキップすることがある。空の div や span は削除するか、&nbsp; を入れて実体を持たせる。とくに WordPress の自動整形機能(wpautop)が余分な p タグや br タグを挿入することで、意図しない空要素が生まれることもある。

複雑なHTML構造はカスタムフィールドやショートコードに置き換える

テーブルやフォーム、装飾を多用したマークアップは、カスタムHTMLブロック1つにまとめるよりも、ACF(Advanced Custom Fields)のテキストエリアフィールドや、ショートコード化して出力する方が安定する。とくにクライアントが自分で編集する前提のサイトでは、カスタムHTMLブロックを直接触らせると今回のようなトラブルが再発しやすい。テーマ側でテンプレートパーツとして管理し、投稿画面では入力欄だけを提供する設計が安全だ。

今後同様のトラブルを防ぐための運用ポイント

今後同様のトラブルを防ぐための運用ポイント

カスタムHTMLブロックに貼り付ける前にコード検証を行う

外部の HTML スニペットをそのまま貼り付ける前に、W3C のバリデーターや VSCode の構文チェック機能でタグの閉じ忘れや文法エラーがないかを確認する。コピー元の Web ページから取得したコードには、不要な属性や非推奨のタグが混じっていることが多い。貼り付け前に一度テキストエディターにペーストし、明らかな問題を取り除いてからブロックに入力する習慣をつける。

WordPress と Gutenberg を最新バージョンに保つ

Gutenberg プラグインを単体でインストールしている場合は、定期的に更新することでブロックパーサーの改善や既知の不具合修正が適用される。コア組み込みのブロックエディターであっても、WordPress 本体のアップデートによってパースエンジンの挙動が改良されることがある。編集画面でカスタムHTMLブロックが空になる現象の一部は、過去のバージョンで修正されたバグに起因している可能性もあるため、まずは最新の状態であることを確認する。

どうしても必要な場合はコードエディターモードを常用する

ビジュアルエディターで空になってしまう HTML をどうしてもページに残したいときは、編集時にコードエディターモードに切り替えて作業する方法が最終的な回避策になる。データベースにコードが正しく保存されているなら、コードエディター表示では常に中身が確認できる。手間は増えるが、複雑なマークアップや埋め込みコードを扱うページでは、あらかじめこの運用を前提にしておくとトラブルが起きても編集が止まらない。

よくある質問

カスタムHTMLブロックの中身は完全に消えたのか

ビジュアルエディターでは空に見えても、コードエディターモードに切り替えると HTML が残っていることがほとんどだ。データベースの post_content にも保存されているため、失われたわけではない。

プラグインをすべて無効化しても直らない場合はどうすればいいか

テーマの functions.php や使用中の子テーマがエディターの動作に干渉している可能性がある。標準テーマ(Twenty Twenty-Five など)に一時的に切り替えて再現するか確認し、テーマ側のフィルターやアクションを調査する。

script タグをカスタムHTMLブロックで使う正しい方法はあるか

原則としてカスタムHTMLブロック内の script タグは推奨されない。管理画面でのレンダリング問題に加え、セキュリティプラグインやサーバー側のフィルターで除去されるリスクもある。どうしても必要な場合はショートコード化するか、wp_enqueue_script でテーマに登録するのが安全だ。

ビジュアルエディターで空白になる特定の HTML タグの一覧はあるか

公式の一覧は存在しないが、script タグ、style タグ、iframe、object、embed、空の div や span、コメントアウト行、複雑に入れ子になったインラインスタイル付き要素などが報告されている。問題が起きたら該当タグを一つずつ検証するのが確実だ。

ビジュアルエディターで空になる現象は Gutenberg のバグなのか

仕様とバグの両面がある。script タグの実行制限はセキュリティ上の意図的な制限であり、空要素のパース失敗はブロックエディターの改善が期待される領域だ。WordPress コアのバージョンアップによって挙動が変わることもあるため、最新バージョンへの更新が有効な場合が多い。

この記事のポイント

  • カスタムHTMLブロックが空に見えてもデータはデータベースに残っている
  • script タグや空要素、閉じタグ省略がパース失敗の主原因
  • コードエディターモードで中身を確認し原因タグを特定する
  • 複雑な HTML はカスタムフィールドやショートコードで管理する
  • WordPress と Gutenberg の最新化で改善するケースがある
ハッキングされたWordPressサイトの完全復旧手順と再発防止策

ハッキングされたWordPressサイトの完全復旧手順と再発防止策

ハッキングされた WordPress サイトを復旧するには、マルウェアの削除だけでは不十分であり、SFTP や SSH といったホスティングレベルの認証情報をすべてローテーションし、バックドアを残さず完全に除去した上で、数週間にわたって再感染していないか監視を続ける必要がある。

WordPress がハッキングされる主な原因はどこにあるのか

WordPress がハッキングされる主な原因はどこにあるのか

侵入経路として圧倒的に多いのが、更新されていないプラグインやテーマの脆弱性だ。Patchstack の統計では、WordPress エコシステム全体の脆弱性の約96%がプラグイン、残り4%がテーマに存在し、コア本体に起因するものはごくわずかである。脆弱性が公表され修正パッチがリリースされると、攻撃者は数時間以内にそのバージョンを標的としたスキャンを開始する。

もう一つの主な侵入口は、弱いパスワードや使い回しの認証情報、あるいはフィッシング詐欺などで盗まれたログイン情報だ。攻撃者は WordPress の管理画面だけでなく、SFTP や SSH、ホスティングコントロールパネルといった上位のアクセス権も狙う。

侵入に成功した攻撃者は、すぐに目立った改ざんを行うとは限らない。ファイルに巧妙に偽装したバックドアを仕込み、追加の管理者アカウントを作成した後、数週間から数カ月潜伏するケースが一般的だ。そのため、サイト改ざんの直前に取得したバックアップであっても、見えないバックドアがすでに仕込まれている可能性を前提に復旧作業を進めなければならない。

侵入経路 脆弱性のあるプラグインやテーマ、盗まれた認証情報
潜伏行動 バックドアの設置、管理者アカウントの追加、SSH 鍵の登録
表面化 サイト改ざんやリダイレクト、検索結果の汚染など
攻撃者の動き 潜伏期間があるため、直近のバックアップも汚染されている可能性が高い

上図の流れが示すとおり、攻撃者はサイトに侵入したあと長期間潜伏し、ファイルやデータベースの奥深くに復旧を妨げる仕掛けを残す。

完全な掃除と再発防止のための5ステップ

完全な掃除と再発防止のための5ステップ

証拠を保全しログを収集する

復旧作業に着手する前に、改ざんされた状態のサイト全体(全ファイルとデータベースダンプ)のバックアップを取得し、証拠として安全な場所に保管する。ログの収集も同時に行う。利用しているサーバー会社へ、ウェブアクセスログ、SSH ログ、SFTP ログを依頼する。ログの保存期間はサーバー会社によって異なり、ウェブアクセスログは48時間程度、SFTP ログは提供されない場合もあるため、できるだけ早く依頼する必要がある。

解析ツールを使って感染範囲を特定する

取得したバックアップとログを解析する。AI を活用した解析ツールを使うと、大量のファイルからパターンを検出しやすい。もし感染前のクリーンなバックアップが存在するなら、それと比較することで未知のバックドアの発見率が大幅に上がる。スキャン範囲は wp-content ディレクトリ内だけに限定せず、ドキュメントルート全体とし、フォントファイルやキャッシュディレクトリに偽装されたシェルスクリプトも見落とさないようにする。

見つかったマルウェアと不正アカウントを削除する

検出された悪意のあるファイルは無効化ではなく削除する。単にプラグインを無効化しただけでは、該当ファイルに直接 URL でアクセスされると動作してしまう。また、管理画面のユーザー一覧に表示されない管理者アカウントや、データベースに直接埋め込まれた不正なエントリも除去する。

すべての認証情報をローテーションする

ファイルの掃除が完了しても、認証情報が漏洩したままだと再感染を繰り返す。WordPress のソルト(暗号化用の乱数文字列)とデータベースのパスワードを変更し、全ユーザーのパスワードをリセットする。さらに、SFTP のパスワード、SSH の公開鍵(身に覚えのない鍵はすべて削除)、API トークン、ホスティングのコントロールパネルのパスワードもすべて変更し、多要素認証を有効にする。作業に関わった全員が、自分のパソコンをマルウェアスキャンし、FTP クライアントにパスワードを保存する習慣をやめることも忘れてはならない。

WordPress 層 ソルトの変更、DBパスワードの変更、全ユーザーのパスワードリセット
ホスティング層 SFTP パスワードと SSH 鍵の変更、API トークンの再発行、コントロールパネルのMFA有効化
端末層 作業者のマルウェアスキャン、FTP クライアントからのパスワード削除
WordPress層 ホスティング層 端末層

この三層すべてで認証情報を更新しない限り、攻撃者は残った認証情報を使って何度でも侵入できる。

再感染の有無を監視し、クリーンな状態を維持する

一度の掃除で完全に除去できたと判断してはならない。掃除中に攻撃者が再侵入している可能性もあるため、作業が完了したら新しいバックアップとログを取得し、最初の解析ステップから再度実行する。このサイクルを、少なくとも二回連続で異常が検出されなくなるまで繰り返す。

本当にクリーンだと確信できるバックアップが取得できたら、それを信頼できるソース・オブ・トゥルース(基準点)として保管する。その後の定期バックアップはすべてこの基準点と比較し、差分が発生した瞬間を検知できるようにする。サイトが改ざんされる前に異常を捉えるには、復旧後も数週間は毎日バックアップとスキャンを継続し、問題がなければ監視頻度を徐々に落としていく方法が現実的だ。

アクセス権限を「必要性」で見直す

アクセス権限を「必要性」で見直す

WordPress の管理者アカウント、SFTP ユーザー、SSH 鍵、ホスティングのコントロールパネルユーザーは、それぞれが攻撃者にとっての侵入口になり得る。信頼できる人物であっても、その人が使うパソコンがマルウェアに感染したり、パスワードがフィッシング詐欺で盗まれたりすれば、そのアカウントは攻撃者に利用される。

そのため、アクセス権限は「信頼」ではなく「業務上の必要性」だけを基準に付与する。サイトの編集者に管理者権限は必要ないし、たまにコンテンツを修正するだけの担当者に SFTP アカウントは不要だ。経営者であっても、サーバーの操作が業務に含まれないならば管理者アクセスを持つべきではない。権限を持つアカウントの数を最小限に減らし、それぞれの権限レベルも必要最低限に絞ることが、最も低コストで効果的な防御策となる。

Before(信頼ベース) 編集者にも管理者権限 経営者にサーバー管理者権限 元スタッフのアカウントが残存
After(必要性ベース) 編集者は編集者権限 サーバー操作はエンジニアのみ 退職者のアカウントは即時削除

上記のように、権限を必要最低限に絞り込むだけで、攻撃者が悪用できる認証情報の総数は大幅に減る。

よくある質問

バックドアはどこに隠れていることが多いのか

wp-content 内のプラグインやテーマだけではなく、ドキュメントルート直下や、画像アップロードディレクトリ、キャッシュフォルダなどに偽装されるケースが多い。フォントファイル(.woff や .ttf)に偽装した悪意ある PHP コードが埋め込まれている事例もある。スキャンは必ずサイトのルート全体を対象にする必要がある。

データベースにもバックドアは残るのか

残る。管理画面のユーザー一覧に表示されない管理者アカウントや、プラグイン一覧から隠蔽された悪意あるプラグインのエントリがデータベースに直接書き込まれていることがある。ファイルの掃除だけでなく、データベースの直接確認も必須だ。

無料のセキュリティプラグインだけで復旧できるのか

セキュリティプラグインは感染の検知や予防には有効だが、すでに深く侵入されたサイトの完全な復旧を保証するものではない。特にホスティングレベルの認証情報が漏洩している場合、プラグインのスキャンでは検出できない経路から再侵入される。認証情報のローテーションと継続的なスキャンの組み合わせが不可欠になる。

復旧後、いつまで監視を続ければよいのか

少なくとも2週間は毎日のバックアップとスキャンを継続するのが現実的な目安だ。攻撃者が盗んだ認証情報をしばらく寝かせてから使うケースもあるため、数日間問題がなかったというだけで監視をやめてはいけない。2週間以上経過し、その間の全スキャンで異常がなければ、監視頻度を週に数回へ徐々に落としてもよい。

WP CLI を使わずに復旧作業は可能か

可能だが、手作業でのファイルの確認やデータベースの直接操作が必要になるため、作業時間と見落としのリスクが増加する。WP CLI に抵抗がある場合は、信頼できるエンジニアに依頼する方が安全だ。サーバー会社によっては、マルウェアスキャンと駆除の有償サービスを提供しているところもある。

この記事のポイント

  • ハッキングの侵入経路はプラグインやテーマの脆弱性、および漏洩した認証情報が大半を占める
  • バックドアはファイルとデータベースの両方に潜伏し、直近のバックアップも汚染されている前提で作業する
  • 復旧にはファイル削除と同時に、WordPress、ホスティング、作業端末の三層で認証情報のローテーションが必須
  • アクセス権限は「信頼」ではなく「必要性」で付与し、不要なアカウントは即座に削除する
  • 一度の掃除で完了とせず、数週間の継続監視と複数回のスキャンで再感染の有無を確認する