Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

WordPress: WP-Cron and the Action Scheduler

WP-Cron is WordPress’s built-in scheduling system, and it comes with a well-known quirk worth knowing before you rely on it: it doesn’t run on a real system timer. Instead, it checks whether any scheduled task is due every time a visitor loads a page, which means a site with no traffic can silently stop running its scheduled tasks on time.

add_action('daily_report_hook', function () {
    // generate and email the report
});

if (!wp_next_scheduled('daily_report_hook')) {
    wp_schedule_event(time(), 'daily', 'daily_report_hook');
}

The standard fix, for anything that actually matters, is disabling WordPress’s page-load trigger and calling it from a real system cron job instead:

// wp-config.php
define('DISABLE_WP_CRON', true);
# real crontab entry, running every 5 minutes
*/5 * * * * curl https://example.com/wp-cron.php?doing_wp_cron >/dev/null 2>&1

For anything beyond simple scheduled hooks, especially queued background work like sending bulk emails or processing an import, the Action Scheduler library (bundled with WooCommerce, but usable standalone) adds a proper queue with retries and logging on top of the same underlying idea:

as_schedule_single_action(time(), 'process_import_batch', ['batch_id' => 42]);

add_action('process_import_batch', function ($batch_id) {
    // process it
});

When to reach for this

WP-Cron with a real system crontab for anything time-sensitive: scheduled reports, subscription renewals, cache warming. Action Scheduler once you need retries, batching, or visibility into whether a background task actually succeeded.

When it’s the wrong fit

High-frequency or high-reliability background processing, where Laravel’s queue system or Symfony Messenger, both built around a real queue backend from the start, offer a more solid foundation than WordPress’s page-load-triggered model.

Under the hood: WP-Cron’s “check on every page load” design is a direct consequence of typical shared WordPress hosting historically not allowing users to configure real system cron jobs. It’s a reasonable workaround for that constraint, not a design flaw exactly, but one worth overriding the moment your hosting does allow real cron.