WordPress’s scheduler has a quirk that surprises everyone eventually: wp-cron is not a server cron job — it triggers opportunistically whenever a visitor loads a page. The result: backup jobs, email queues, and update checks run during someone’s page request, at random moments. Here is how to see it, fix it, and keep it fixed.
Why wp-cron hurts
The trigger mechanism: on page load, WordPress checks if scheduled events are due, then fires them (by default in the same request loop via loopback, or spawning a parallel request). A heavy scheduled task — backup, bulk optimization — lands mid-day-traffic and eats a PHP worker, exactly the pattern behind mysterious TTFB spikes and 503s on small hosting.
The fix: real cron
- Disable the opportunistic trigger in wp-config.php:
define( 'DISABLE_WP_CRON', true ); - Add a server cron hitting wp-cron.php every 5–15 minutes:
*/5 * * * * curl -s https://yoursite.com/wp-cron.php?doing_wp_cron >/dev/null(orwp cron event run --due-now --allow-rootvia WP-CLI). - Verify events run: WP Crontrol (plugin) lists scheduled events and their next run — anything overdue confirms the trigger is broken.
Scheduled work now happens at predictable times, off the visitor path. Every site we migrate gets this — the checklist item in the migration guide exists because cron dies silently in moves.
Audit what is scheduled
The second problem: too much scheduled work regardless of trigger. WP Crontrol shows every event — and the surprise is usually volume: dozens of plugins each with recurring tasks, some every minute.
- Heavy hitters to reschedule: backup plugins (run at night, not hourly), image optimizers (batch, don’t churn), database cleanups (nightly).
- Suspicious events: tasks from plugins you removed (orphaned events persist), and anything firing more often than its purpose justifies.
- Long-runners: events that take minutes block workers — the same capacity math as admin-ajax load.
The action-scheduler layer
WooCommerce and several ecosystem plugins use Action Scheduler rather than wp-cron — its pending/failed queue is visible under Tools → Scheduled Actions. A backed-up queue (thousands of pending actions) is its own performance problem and its own diagnostic: failed retries pile up after API outages. Clear stale batches deliberately, not blindly.
Verification
After the change: cron events run within their schedule (WP Crontrol’s next-run times advance), page-load TTFB loses its spikes, and the server’s process list shows cron activity at scheduled times instead of random ones. The free audit checks the cron configuration among its infrastructure items.
WordPress has no daemon: it checks for due scheduled events whenever a request comes in and fires them opportunistically. Heavy jobs therefore execute during visitor traffic — the design becomes a problem as sites grow. Define DISABLE_WP_CRON as true in wp-config.php, then add a server cron hitting wp-cron.php every 5–15 minutes. Verify with WP Crontrol that events resume on schedule. The queue system WooCommerce and ecosystem plugins use for background jobs. Tools → Scheduled Actions shows pending and failed items — a backed-up queue is a performance problem of its own. Either DISABLE_WP_CRON is set without a server cron replacing it (common after migrations), or loopback requests are blocked. WP Crontrol's overdue events reveal it immediately.Why does wp-cron run on page loads?
How do I switch WordPress to real cron?
What is Action Scheduler in WordPress?
Why do my cron events never run?