WP-Cron is WordPress’s built-in task scheduler: plugins and core register events (publishing scheduled posts, sending emails, running backups, processing WooCommerce actions), and WordPress checks for due events on page loads, running them through a background request to wp-cron.php. Because it depends on visits, it is not precise; a real server cron job fixes that.
This guide is part of my automation series.
How does WP-Cron work?
On each page load, WordPress checks whether any scheduled event is due. If so, it sends a non-blocking request to wp-cron.php, which runs the due events. No visits means no checks, so tasks run late on quiet sites. Events are stored in the cron option in the database.
Problems with the default behaviour
- Missed schedules on low-traffic sites: a post scheduled for 9:00 publishes when the next visitor arrives.
- Page caching: if a full-page cache serves pages without running PHP, WordPress may not trigger cron from those visits.
- Loopback failures: some hosts or firewalls block the site from requesting itself, so
wp-cron.phpnever runs. Site Health reports this. - Load on busy sites: frequent checks, and long tasks running inside web requests.
- WooCommerce dependence: Action Scheduler, which WooCommerce uses for webhooks, emails and background jobs, relies on WP-Cron by default (plus async requests).
Step 1: see what is scheduled
With WP-CLI:
wp cron event list --fields=hook,next_run_relative,recurrence
wp cron schedule list
wp cron test
Or install the WP Crontrol plugin to view, run and edit events in the admin. Look for:
- Events overdue by a long time.
- Hooks from plugins you removed (orphaned events).
- Very frequent schedules that do heavy work.
Step 2: disable the page-load trigger
In wp-config.php, above the “That’s all, stop editing” line:
define( 'DISABLE_WP_CRON', true );
This only stops visits from triggering cron. Events still exist and still need to run, so do step 3 immediately.
Step 3: add a real cron job
In your hosting control panel (cPanel, hPanel and similar have a “Cron Jobs” section) or with crontab -e on a server.
Option A: WP-CLI (preferred when available)
*/5 * * * * cd /home/user/public_html && wp cron event run --due-now --quiet
Option B: request wp-cron.php
*/5 * * * * curl -s https://example.com/wp-cron.php?doing_wp_cron > /dev/null 2>&1
Option C: PHP directly
*/5 * * * * cd /home/user/public_html && php wp-cron.php > /dev/null 2>&1
WP-CLI and direct PHP avoid going through the web server, firewall and cache, which makes them more reliable. Make sure the cron job runs as the same user that owns the site files.
Step 4: verify
- Schedule a test post a few minutes ahead and check it publishes.
- Run
wp cron event listand confirm no events are long overdue. - Check Tools → Scheduled Actions in WooCommerce for pending actions piling up.
- Check Site Health for scheduled event warnings.
Scheduling your own events
In a plugin:
register_activation_hook( __FILE__, function () {
if ( ! wp_next_scheduled( 'mx_daily_cleanup' ) ) {
wp_schedule_event( time() + HOUR_IN_SECONDS, 'daily', 'mx_daily_cleanup' );
}
} );
register_deactivation_hook( __FILE__, function () {
wp_clear_scheduled_hook( 'mx_daily_cleanup' );
} );
add_action( 'mx_daily_cleanup', function () {
// Keep it short; move heavy work into batches.
delete_expired_transients();
} );
Good habits:
- Schedule on activation, clear on deactivation; do not call
wp_schedule_event()on every page load without checking. - Keep each run short; process large jobs in batches across runs.
- Make tasks safe to run twice.
- Log failures somewhere you will see them.
Custom intervals
add_filter( 'cron_schedules', function ( $schedules ) {
$schedules['every_fifteen_minutes'] = array(
'interval' => 15 * MINUTE_IN_SECONDS,
'display' => __( 'Every 15 minutes', 'mx' ),
);
return $schedules;
} );
Remember: WP-Cron intervals are minimums, not guarantees. With a 5-minute server cron, a 1-minute schedule still runs about every 5 minutes.
Real cron for heavier automation
For tasks like supplier CSV imports, sitemap pings or report generation, I often skip WP-Cron and call a custom WP-CLI command directly from the server cron at a fixed time. It is explicit, logged and independent of WordPress’s queue.
Checklist
- Scheduled events reviewed; orphaned hooks removed
DISABLE_WP_CRONset and a server cron job added- Cron job tested with a scheduled post
- WooCommerce scheduled actions not piling up
- Custom events scheduled on activation and cleared on deactivation
Common mistakes
- Setting
DISABLE_WP_CRONand forgetting the server cron: emails, backups and scheduled posts silently stop. - Running the server cron every minute on a slow shared host with heavy tasks.
- Leaving events from deleted plugins firing errors.
- Doing heavy imports inside a single cron run that times out.
If scheduled tasks on your site are unreliable, contact me.
Frequently asked questions
What is WP-Cron?
WP-Cron is WordPress's built-in scheduler. It stores scheduled events in the database and runs any that are due when a page is loaded, by sending a background request to wp-cron.php.
Should I disable WP-Cron?
Disable the page-load trigger only if you replace it with a real server cron job. Otherwise scheduled posts, emails, backups and WooCommerce tasks stop running.
How often should the real cron job run?
Every 5 minutes suits most sites. Busy stores with time-sensitive tasks may use every minute; very quiet sites may use 15 minutes.
Why are my scheduled posts missing schedule?
Usually WP-Cron is not running: low traffic, DISABLE_WP_CRON set without a server cron, loopback requests blocked, or caching preventing PHP from running.


