2026年5月19日

2026年5月19日

WordPressのWP-Cronエラーを解決する方法

はじめに

WordPressでwp_schedule_event()でスケジュールを登録したのにタスクが実行されない・wp_next_scheduled()falseを返してスケジュールが登録されているはずなのに次回実行時刻が取得できない・プラグインを再有効化するたびにCronイベントが重複登録される・アクセスが少ないサイトでWP-Cronがほとんど動作しないといった問題は、WordPressのWP-Cronシステムの仕組みを理解することで解決できます。

症状・原因

  • wp_schedule_event()で登録したフックアクションがadd_action()でリスナー関数に紐付けられていない
  • wp_next_scheduled()で引数($args)を渡す場合、登録時と同じ配列を渡さないと別のイベントとして扱われる
  • プラグイン有効化時に重複チェックなしでwp_schedule_event()を呼んでいるため、同一イベントが複数登録される
  • DISABLE_WP_CRON = trueがwp-config.phpで設定されており、WP-Cronが無効化されている

解決手順

ステップ1:WP-Cronの状態を診断する

# 登録済みCronイベントを確認
wp cron event list

# 次回実行予定を確認
wp cron event list --fields=hook,next_run_relative,schedule

# 特定のイベントが登録されているか確認
wp eval "var_dump(wp_next_scheduled('my_plugin_cron_hook'));"

# Cronを手動で実行(テスト)
wp cron event run my_plugin_cron_hook

# WP-Cronが有効か確認
wp eval "echo defined('DISABLE_WP_CRON') && DISABLE_WP_CRON ? '無効' : '有効';"

# 重複したCronイベントを確認
wp eval "print_r(_get_cron_array());"

ステップ2:WP-Cronを正しく実装する

// ✅ プラグイン有効化時にCronを登録(重複チェック必須)
register_activation_hook(__FILE__, 'my_plugin_activate_cron');

function my_plugin_activate_cron(): void {
    // ✅ wp_next_scheduled で重複チェックしてから登録
    if (!wp_next_scheduled('my_plugin_hourly_hook')) {
        wp_schedule_event(
            time(),                 // 最初の実行時刻(Unix タイムスタンプ)
            'hourly',               // スケジュール: hourly, twicedaily, daily, weekly
            'my_plugin_hourly_hook' // フックアクション名
        );
    }
}

// ✅ プラグイン無効化時にCronを削除
register_deactivation_hook(__FILE__, 'my_plugin_deactivate_cron');

function my_plugin_deactivate_cron(): void {
    $timestamp = wp_next_scheduled('my_plugin_hourly_hook');
    if ($timestamp) {
        wp_unschedule_event($timestamp, 'my_plugin_hourly_hook');
    }
    // または全スケジュールを一括削除
    wp_clear_scheduled_hook('my_plugin_hourly_hook');
}

// ✅ Cronフックにコールバックを登録(必ず add_action で紐付ける)
add_action('my_plugin_hourly_hook', 'my_plugin_do_hourly_task');

function my_plugin_do_hourly_task(): void {
    // ✅ タスク実行中のロック(重複実行防止)
    if (get_transient('my_plugin_cron_running')) {
        return;
    }
    set_transient('my_plugin_cron_running', true, 5 * MINUTE_IN_SECONDS);

    try {
        // タスクの処理
        my_plugin_process_queue();
    } finally {
        delete_transient('my_plugin_cron_running');
    }
}

ステップ3:カスタムスケジュール間隔を追加する

// ✅ カスタムスケジュール間隔を追加
add_filter('cron_schedules', function(array $schedules): array {
    // 5分ごと
    $schedules['every_five_minutes'] = [
        'interval' => 5 * MINUTE_IN_SECONDS,
        'display'  => '5分ごと',
    ];

    // 週2回
    $schedules['twice_weekly'] = [
        'interval' => 3.5 * DAY_IN_SECONDS,
        'display'  => '週2回',
    ];

    // 月1回
    $schedules['monthly'] = [
        'interval' => 30 * DAY_IN_SECONDS,
        'display'  => '月1回',
    ];

    return $schedules;
});

// ✅ カスタム間隔を使ってスケジュール登録
if (!wp_next_scheduled('my_plugin_five_min_hook')) {
    wp_schedule_event(time(), 'every_five_minutes', 'my_plugin_five_min_hook');
}

// ✅ 引数付きのCronイベント
function my_plugin_schedule_with_args(int $user_id): void {
    $args = [$user_id]; // 引数は配列で渡す

    // ✅ 登録と取得で同じ $args を渡す
    if (!wp_next_scheduled('my_plugin_user_hook', $args)) {
        wp_schedule_single_event(
            time() + HOUR_IN_SECONDS,
            'my_plugin_user_hook',
            $args
        );
    }
}
add_action('my_plugin_user_hook', function(int $user_id): void {
    // $user_id を使って処理
    update_user_meta($user_id, 'last_cron_processed', time());
}, 10, 1);

ステップ4:WP-Cronの信頼性を向上させる

// ✅ wp-config.php の設定を確認
// define('DISABLE_WP_CRON', false); // WP-Cronを有効化
// define('WP_CRON_LOCK_TIMEOUT', 120); // ロックタイムアウトを延長(デフォルト60秒)

// ✅ アクセスが少ないサイトはサーバーのcronで実行
// crontab に追加:
// */5 * * * * wget -q -O - https://example.com/wp-cron.php?doing_wp_cron > /dev/null 2>&1
// または WP-CLI を使用:
// */5 * * * * cd /var/www/html && wp cron event run --due-now --quiet

// ✅ WP-Cronの実行をログに記録
add_action('my_plugin_hourly_hook', function(): void {
    error_log(sprintf(
        '[%s] my_plugin_hourly_hook started (memory: %s)',
        date('Y-m-d H:i:s'),
        size_format(memory_get_usage())
    ));
}, 1);

// ✅ Cronの実行状況を管理画面で確認できるようにする
add_action('admin_notices', function(): void {
    if (!current_user_can('manage_options')) {
        return;
    }

    $next = wp_next_scheduled('my_plugin_hourly_hook');
    if (!$next) {
        echo '<div class="notice notice-error"><p>';
        echo 'WP-Cron: my_plugin_hourly_hook が登録されていません。';
        echo '</p></div>';
    } elseif ($next < time() - HOUR_IN_SECONDS) {
        echo '<div class="notice notice-warning"><p>';
        printf('WP-Cron: my_plugin_hourly_hook の次回実行が %d 分遅延しています。', (time() - $next) / 60);
        echo '</p></div>';
    }
});

ステップ5:Cronジョブのバッチ処理

// ✅ 大量データをバッチ処理する
function my_plugin_process_queue(): void {
    $batch_size = 50;
    $offset     = (int) get_option('my_plugin_cron_offset', 0);

    $items = get_posts([
        'post_type'      => 'my_post_type',
        'post_status'    => 'publish',
        'posts_per_page' => $batch_size,
        'offset'         => $offset,
        'fields'         => 'ids',
        'no_found_rows'  => true,
    ]);

    if (empty($items)) {
        // 全処理完了:オフセットをリセット
        update_option('my_plugin_cron_offset', 0);
        return;
    }

    foreach ($items as $post_id) {
        // 各アイテムを処理
        update_post_meta($post_id, '_processed_at', time());
    }

    // 次のバッチのためにオフセットを更新
    update_option('my_plugin_cron_offset', $offset + $batch_size);

    // ✅ 次のバッチをすぐに実行(まだ残っている場合)
    if (count($items) === $batch_size) {
        wp_schedule_single_event(time() + 30, 'my_plugin_hourly_hook');
    }
}

注意事項

  • WP-Cronはサイトへのアクセス時にのみ実行されます。アクセス数が少ないサイトでは、設定した間隔通りに実行されないことがあります。本番環境ではDISABLE_WP_CRON = trueを設定してサーバーのcron(crontab)からWP-CLIで実行する方法を推奨します
  • wp_schedule_event()を呼ぶ際は必ず事前にwp_next_scheduled()で重複チェックしてください。プラグインを更新するたびに有効化フックが実行される環境では、チェックなしで登録するとCronイベントが無限に重複します

まとめ

WordPressのWP-Cronエラーの解決は①wp cron event listで登録確認・wp cron event runで手動テスト・DISABLE_WP_CRON設定確認・_get_cron_array()で重複確認、②有効化フックでwp_next_scheduled()チェック後にwp_schedule_event()・無効化フックでwp_clear_scheduled_hook()add_action(フック名, コールバック)で必ず紐付ける、③cron_schedulesフィルターでカスタム間隔追加・引数付きイベントは登録と取得で同じ$args配列、④アクセスが少ないサイトはサーバーcron+WP-CLIで確実に実行・WP_CRON_LOCK_TIMEOUTを延長・admin_noticesで遅延を検知、⑤大量データはバッチ分割してget_optionでオフセット管理・Transientでロックして重複実行を防止の手順で解決します。

お気軽にご相談ください

お見積りへ お問い合わせへ