WP-Cron vs. Real Cron: Getting Scheduled Tasks Right

Arafat Islam Sep 5, 2026 4 min read
WP-Cron vs. Real Cron: Getting Scheduled Tasks Right

WordPress's scheduling system — used for publishing scheduled posts, checking for updates, sending scheduled emails, cleaning up transients, and any plugin-defined scheduled task — is not a real system-level cron job by default, despite the name "WP-Cron" suggesting otherwise. Understanding the actual mechanism explains most of the confusing symptoms it causes.

How WP-Cron actually works

By default, WordPress checks whether any scheduled task is due on every page load — a visitor requesting any page triggers a check of wp_options for due cron events, and runs them if any are found (via an async request WordPress fires to wp-cron.php). There's no persistent background process; scheduling is entirely piggybacked on regular site traffic.

Why this causes real problems

  • Low-traffic sites delay scheduled tasks. If nobody visits the site, nothing triggers the check — a scheduled post meant to publish at 9am might not actually publish until the next visitor arrives, however much later that is.
  • High-traffic sites can over-trigger it. Every page load technically attempts to check for due tasks (WordPress does have some built-in throttling against firing the check on truly every request), which adds unnecessary overhead on busy sites.
  • Full-page caching makes it worse. If a page is served from cache, WordPress's PHP never actually executes for that request — so the WP-Cron check embedded in the request cycle never fires either. Heavily cached sites (which is most well-optimized sites) can end up with WP-Cron rarely triggering in practice.
  • It's not exactly on schedule anyway. Even under ideal conditions, this design means "due" tasks run on the next qualifying request, not at the precise scheduled time — for most use cases that's fine, but it's worth knowing it was never a precision guarantee.

The fix: disable WP-Cron's traffic-triggered behavior and use real system cron

Add to wp-config.php:

define('DISABLE_WP_CRON', true);

Then set up an actual server-level cron job (via your host's cron scheduling, or a real crontab entry) to trigger wp-cron.php on a fixed interval:

*/5 * * * * curl -s https://example.com/wp-cron.php > /dev/null 2>&1

This decouples scheduled task execution from visitor traffic entirely — tasks run reliably on the interval you set, regardless of whether the site had any visitors, and regardless of full-page caching serving most requests from cache.

Choosing the right interval

Every 5–15 minutes is a reasonable default for most sites — frequent enough that scheduled posts and time-sensitive tasks aren't meaningfully delayed, infrequent enough not to add unnecessary load. Sites with more time-sensitive scheduling needs (frequent scheduled publishing, time-critical plugin tasks) can go tighter; low-activity sites can go looser.

Managed hosting often already handles this

Many managed WordPress hosts disable traffic-triggered WP-Cron and run it via real server cron automatically as part of their platform — worth checking your specific host's documentation or dashboard before manually configuring DISABLE_WP_CRON, since setting it up twice (both host-managed and self-configured) can create duplicate execution.

Debugging "my scheduled task didn't run"

  • Check whether DISABLE_WP_CRON is set and whether a real cron job is actually configured to hit wp-cron.php — a common failure is disabling traffic-triggered cron without setting up the replacement, which stops scheduling entirely.
  • Verify the scheduled event is actually registered as expected (a WP-CLI cron-listing command, or a debugging plugin, can show pending scheduled events).
  • Confirm the site isn't blocking its own wp-cron.php requests via a firewall rule or security plugin that (incorrectly) treats the cron endpoint as suspicious traffic.

The takeaway

WP-Cron's traffic-dependent design is a reasonable default for a system with no hosting requirements, but it's the wrong choice once a site has real caching or scheduling precision needs — which is most production sites. Disabling the traffic-triggered version and running it from real system cron on a fixed interval is a small, permanent fix for an entire category of "why didn't this run on time" bugs.