2026年5月20日
2026年5月20日
WordPressの定期セキュリティスキャンの設定|Wordfence・WP-CLI・cronで自動化
WordPress の改ざんやマルウェアは、表示が崩れるなどの症状が出るまで気づかないことがよくあります。定期的にスキャンして、結果をメールで受け取る仕組みを作っておくと、早く気づけます。
この記事では、次の3つの方法を説明します。組み合わせて使ってかまいません。
| 方法 | 向いている場面 | 必要なもの |
|---|---|---|
| Wordfence の自動スキャン | 管理画面だけで設定したい | プラグイン |
| WP-CLI のスクリプトを cron で実行 | サーバーに SSH で入れる | WP-CLI、cron、メール送信 |
| WP-Cron で動く自作のチェック | 管理者の追加や更新の有無を知らせたい | mu-plugins にファイルを置ける |
WP-CLI のコマンドと Wordfence の仕様は、それぞれの公式ドキュメントで確認したものです(2026年11月時点)。
まず手で状態を確かめる
WP-CLI が使えるなら、WordPress のディレクトリで次のコマンドを実行します。
# コアファイルが公式の配布物と同じか調べる
wp core verify-checksums
# wordpress.org で配布されているプラグインのファイルが配布物と同じか調べる
wp plugin verify-checksums --all
# 更新のあるプラグイン
wp plugin list --update=available
# WordPress 本体の更新
wp core check-update
# 管理者の一覧(覚えのないアカウントがないか)
wp user list --role=administrator --fields=ID,user_login,user_email,user_registered
wp core verify-checksumsは、wp-adminとwp-includesなどのコアファイルを、WordPress.org が公開しているチェックサムと比べます。書き換えられたファイルや、本来は無いファイルが見つかると表示されます。wp-contentの中(テーマ・プラグイン・アップロードしたファイル)は調べません。wp plugin verify-checksums --allは、wordpress.org のプラグインディレクトリで配布されているプラグインだけを比べます。有料のプラグインや自作のプラグインはチェックサムを取得できず、スキップされます。wp plugin listは更新の有無を見るだけで、脆弱性を調べるものではありません。既知の脆弱性の有無を調べるには、WPScan などの脆弱性データベースを使います(「WPScan で WordPress の脆弱性を調べる」)。
方法1: Wordfence の自動スキャン
Wordfence はプラグイン一覧からインストールできます。WP-CLI なら次のとおりです。
wp plugin install wordfence --activate
Wordfence の自動スキャンの頻度は、無料版と有料版で違います。
- 無料版: クイックスキャンが毎日、フルスキャンが72時間ごとに自動で動きます。実行する時刻は選べません。
- 有料版(Premium): 「Scan Scheduling」でスキャンする曜日と時刻を決められます。
手動のスキャンは無料版でもいつでも実行できます。スキャンの画面と結果の見方は「Wordfence のスキャンの使い方」を参照してください。
スキャンで問題が見つかったらメールで受け取る
Wordfence の「All Options」にある「Email Alert Preferences」で、スキャンで見つかった問題をメールで知らせる設定と、知らせる重大度(Critical・High・Medium・Low のどれ以上か)を選べます。送り先のメールアドレスは、同じ「All Options」の一般設定(「Where to email alerts」)で指定します。同じ問題について、メールは繰り返し送られません。
Wordfence のメールは WordPress のメール送信(wp_mail())で送られます。届かない場合は、WP Mail SMTP などで SMTP 経由の送信を設定します。
方法2: WP-CLI のスクリプトを cron で毎日実行する
SSH でサーバーに入れるなら、WP-CLI のチェックをまとめたスクリプトを cron で毎日動かし、問題があったときだけメールで受け取れます。
/usr/local/bin/wp-security-scan.sh を作ります。WP_PATH と ADMIN_EMAIL は自分の環境に合わせます。
#!/bin/bash
# WordPress の定期チェック。問題があったときだけメールを送る
set -u
WP_PATH="/var/www/html"
ADMIN_EMAIL="admin@example.com"
REPORT="$(mktemp)"
FOUND=0
{
echo "=== WordPress 定期チェック: $(date '+%Y-%m-%d %H:%M') ==="
echo "--- コアファイルの整合性 ---"
wp --path="$WP_PATH" core verify-checksums 2>&1 || FOUND=1
echo "--- プラグインの整合性(wordpress.org のもの) ---"
wp --path="$WP_PATH" plugin verify-checksums --all 2>&1 || FOUND=1
echo "--- uploads の中の PHP ファイル ---"
PHP_FILES="$(find "$WP_PATH/wp-content/uploads" -type f \( -iname '*.php' -o -iname '*.phtml' -o -iname '*.phar' \) 2>/dev/null)"
if [ -n "$PHP_FILES" ]; then
echo "$PHP_FILES"
FOUND=1
fi
echo "--- 最近24時間に変更された PHP ファイル(wp-content) ---"
find "$WP_PATH/wp-content" -type f -name '*.php' -mtime -1 2>/dev/null | head -50
echo "--- 更新のあるプラグイン ---"
wp --path="$WP_PATH" plugin list --update=available --fields=name,version,update_version 2>&1
} > "$REPORT"
if [ "$FOUND" -ne 0 ]; then
mail -s "[警告] WordPress 定期チェック $(hostname)" "$ADMIN_EMAIL" < "$REPORT"
fi
rm -f "$REPORT"
{ … } > "$REPORT"で、その回の結果だけを一時ファイルにまとめます。過去のログ全体を検索すると、一度でも出た警告で毎回メールが送られてしまうためです。2>&1で、エラーの出力も結果に含めます。2>&1 >> ファイルの順に書くと、エラーはファイルに入りません。wp core verify-checksumsとwp plugin verify-checksumsは、一致しないファイルがあると終了コードが0以外になります。これを|| FOUND=1で拾います。- 最近変更されたファイルは、プラグインの更新でも増えるため、一覧を参考に出すだけで、メールを送る条件にはしていません。
実行の権限を付け、手で一度実行して結果を確かめます。
sudo chmod 755 /usr/local/bin/wp-security-scan.sh
sudo -u www-data /usr/local/bin/wp-security-scan.sh
mail コマンドを使うには、Ubuntu・Debian では mailutils などのパッケージと、メールを送れる MTA(Postfix など)が必要です。テストのメールが届くことを先に確かめておきます。
echo "test" | mail -s "mail test" admin@example.com
WP-CLI を root で実行すると警告が出て止まるため、cron は WordPress のファイルの所有者(Ubuntu・Debian の Apache・Nginx なら多くは www-data)で設定します。
sudo crontab -u www-data -e
毎日3時30分に実行する例です。
30 3 * * * /usr/local/bin/wp-security-scan.sh
www-data の環境で wp のパスが見つからない場合は、スクリプトの中で /usr/local/bin/wp のように / から始まるパスで書きます。
方法3: WP-Cron で管理者の追加や更新を知らせる
WordPress の中から、前回のチェックより管理者が増えていないか、更新が残っていないか、uploads に PHP ファイルが無いかを毎日調べ、見つかったらメールを送るコードです。テーマを変えても止まらないよう、wp-content/mu-plugins/wpdr-security-check.php として保存します(mu-plugins フォルダーが無ければ作ります)。
<?php
/**
* Plugin Name: 定期セキュリティチェック(毎日メール通知)
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
// 1日1回の予定を登録する(登録済みなら何もしない)
add_action( 'init', function () {
if ( ! wp_next_scheduled( 'wpdr_daily_security_check' ) ) {
wp_schedule_event( time() + HOUR_IN_SECONDS, 'daily', 'wpdr_daily_security_check' );
}
} );
add_action( 'wpdr_daily_security_check', function () {
// WP-Cron の中では管理画面用の関数が読み込まれていないため読み込む
require_once ABSPATH . 'wp-admin/includes/plugin.php';
require_once ABSPATH . 'wp-admin/includes/update.php';
$issues = array();
// 1. 前回のチェックから増えた管理者
$admin_ids = get_users( array( 'role' => 'administrator', 'fields' => 'ID' ) );
$admin_ids = array_map( 'intval', $admin_ids );
$known = get_option( 'wpdr_known_admin_ids' );
if ( is_array( $known ) ) {
foreach ( array_diff( $admin_ids, $known ) as $new_id ) {
$user = get_userdata( $new_id );
$issues[] = '新しい管理者: ' . ( $user ? $user->user_login : $new_id );
}
}
update_option( 'wpdr_known_admin_ids', $admin_ids, false );
// 2. コアとプラグインの更新(更新情報は WordPress が定期的に取得したもの)
$core = get_core_updates();
if ( is_array( $core ) && isset( $core[0]->response ) && 'upgrade' === $core[0]->response ) {
$issues[] = 'WordPress の更新があります: ' . $core[0]->version;
}
foreach ( get_plugin_updates() as $plugin ) {
$issues[] = 'プラグインの更新があります: ' . $plugin->Name . ' ' . $plugin->update->new_version;
}
// 3. uploads フォルダーの中の PHP ファイル(通常は置かれない)
$uploads = wp_get_upload_dir();
if ( is_dir( $uploads['basedir'] ) ) {
$files = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator( $uploads['basedir'], FilesystemIterator::SKIP_DOTS )
);
foreach ( $files as $file ) {
if ( preg_match( '/\.(php\d?|phtml|phar)$/i', $file->getFilename() ) ) {
$issues[] = 'uploads の中の PHP ファイル: ' . $file->getPathname();
}
}
}
if ( $issues ) {
$body = "定期セキュリティチェックで次の項目が見つかりました。\n\n";
$body .= '・' . implode( "\n・", $issues ) . "\n\n";
$body .= '確認日時: ' . wp_date( 'Y-m-d H:i' ) . "\n";
$body .= '管理画面: ' . admin_url() . "\n";
wp_mail( get_option( 'admin_email' ), '[' . get_bloginfo( 'name' ) . '] 定期セキュリティチェック', $body );
}
} );
get_plugins()・get_core_updates()・get_plugin_updates()は管理画面用のファイルにある関数で、WP-Cron の実行中は読み込まれていません。require_onceで読み込まないとエラーで止まります。- 管理者は「数が多いか」ではなく、前回のチェックの時点の一覧と比べて、増えた人を知らせます。初回は一覧を保存するだけです。
- 送り先は「設定」→「一般」の「管理者メールアドレス」です。
このコードは WordPress の中で動くため、WordPress のファイルそのものが書き換えられた場合は、改ざんしたコードに止められるおそれがあります。改ざんの検出は、方法2のようにサーバー側から WP-CLI で調べる方法と組み合わせます。
予定が登録されたか確かめる
wp cron event list --fields=hook,next_run_relative,recurrence | grep wpdr_daily_security_check
# すぐに1回実行して、メールが届くか試す
wp cron event run wpdr_daily_security_check
WP-Cron は、サイトにアクセスがあったときに動く仕組みです。アクセスの少ないサイトでは実行が遅れることがあります。決まった時刻に動かしたい場合は、サーバーの cron から WP-Cron を動かす設定にします(「WP-Cron が動かないときの直し方」)。
コードをやめるときは、ファイルを消してから、残った予定を消します。
wp cron event delete wpdr_daily_security_check
wp option delete wpdr_known_admin_ids
問題が見つかったとき
wp core verify-checksumsでコアファイルの不一致が出た: 自分で書き換えた覚えが無ければ、wp core download --version="$(wp core version)" --skip-content --forceで、同じバージョンのコアファイルを上書きできます(wp-contentとwp-config.phpは変わりません)。ただし、改ざんされていた場合は、侵入の経路(古いプラグイン、漏れたパスワードなど)を塞がないとまた書き換えられます。uploadsに PHP ファイルがあった: 普通の WordPress では置かれないファイルです。中身を確かめ、身に覚えが無ければマルウェアを疑います。
感染が疑われるときの調べ方と対処は「WordPress のマルウェアの検出と駆除」を参照してください。
注意事項
- スキャンはサーバーの CPU とディスクを使います。Wordfence の有料版や cron で時刻を選べる場合は、アクセスの少ない時間帯にします。
- スキャンで異常が無くても、安全とは限りません。プラグインとテーマの更新、管理者のパスワードと二段階認証、バックアップと合わせて運用します。
- メールが届かないと、仕組みがあっても気づけません。設定したら、テストのメールが届くことを確かめます。
まとめ
wp core verify-checksumsとwp plugin verify-checksums --allで、配布物と違うファイルを調べる(wp-contentの中や有料プラグインは対象外)- Wordfence の無料版は、クイックスキャンが毎日、フルスキャンが72時間ごと。「Email Alert Preferences」で通知する重大度を選ぶ
- WP-CLI のスクリプトは、その回の結果だけを見て、問題があるときだけメールを送る。cron は WordPress のファイルの所有者で設定する
- WP-Cron のチェックは mu-plugins に置き、管理画面用のファイルを
require_onceで読み込む