WP-Cron isn’t a real cron job, and that trips up a lot of developers who assume it behaves like the Unix scheduler it’s named after. WordPress only checks for due cron events when a page on the site is loaded — no visitors, no page loads, no cron events firing, no matter how long has passed.
Why this matters on low-traffic sites
A site that gets a handful of visits a day might have a “daily” cron event actually fire every three or four days, whenever traffic happens to trigger the check. For anything time-sensitive — abandoned cart emails, subscription renewals — this delay is a real problem, not a theoretical one.
The fix: disable WP-Cron’s page-load trigger, use real cron
// In wp-config.php
define('DISABLE_WP_CRON', true);
# In the server's actual crontab, running every 5 minutes
*/5 * * * * curl -s https://example.com/wp-cron.php?doing_wp_cron > /dev/null 2>&1
Now cron events run on a genuinely reliable schedule, independent of whether anyone’s visited the site recently.
When WP-Cron itself isn’t enough: Action Scheduler
WooCommerce ships with Action Scheduler, which is a proper job queue built on top of WordPress — it handles retries, queuing thousands of jobs without overwhelming the server at once, and logging failures in a way plain WP-Cron doesn’t:
as_schedule_single_action(time() + 300, 'myplugin_send_followup', ['order_id' => $order_id]);
add_action('myplugin_send_followup', function($order_id) {
$order = wc_get_order($order_id);
if ($order && $order->get_status() === 'completed') {
// send the actual followup
}
});
If Action Scheduler is already loaded (it is, on any site running WooCommerce), it’s usually the better choice over raw WP-Cron for anything beyond simple recurring events — particularly bulk operations, like emailing a large customer segment, where WP-Cron alone would try to do everything in one request and likely time out.