WP-Cron

Quick answer: WP-Cron isn’t a real cron job. It only runs when someone visits your site, which means scheduled tasks on a low-traffic site can be hours late — or fire many times at once on a busy one. Switching to a server cron fixes both problems in about five minutes.

If you’ve ever wondered why a scheduled post published forty minutes late, why a backup didn’t run overnight, or why your hosting dashboard flags wp-cron.php as your most-requested file, this is the explanation.

What WP-Cron actually does

A real cron job is a system-level scheduler. It lives on the server, watches the clock, and runs what it’s told at the time it’s told. It doesn’t care whether anyone is using your website.

WP-Cron is not that. It’s a queue of pending tasks stored in your database, checked on page load. The sequence looks like this:

  1. Someone requests a page on your site.
  2. WordPress loads and, near the end of the request, checks the cron queue.
  3. If anything is overdue, WordPress fires an internal request to wp-cron.php to run it.
  4. Those tasks execute while your visitor’s page is being served.

The critical detail is step one. No visitor, no check. WordPress has no way to know that 3am has arrived if nobody is there to tell it.

The design made sense historically — shared hosting often didn’t give users cron access, and this worked everywhere without configuration. But it produces two opposite failure modes depending on your traffic.

Failure mode one: the quiet site

A brochure site for a local business might get a handful of visits a day, all during working hours. Everything scheduled overnight simply waits.

The 6am scheduled post publishes at 9:15 when the first visitor arrives. The nightly backup runs mid-morning, during your busiest period, competing with real traffic. Update checks happen whenever. Anything scheduled for a window that’s already passed by the time someone visits just runs late, every time.

Worse, if a task is supposed to run hourly and your site gets visits twice a day, it doesn’t run twelve times to catch up. It runs once. Twenty-two hours of intended work quietly doesn’t happen.

Failure mode two: the busy site

The opposite problem, and the more expensive one. On a site with steady traffic, every request checks the queue. Under concurrent load, several requests can find the same overdue task and each spawn their own wp-cron.php call before any of them finishes and clears it.

Now you have the same expensive job running multiple times simultaneously. If it’s something heavy — a backup, a large import, an email batch — you get duplicated work, spiking database load, and occasionally duplicated output. People who receive the same newsletter twice have usually met this bug.

It also means a slow scheduled task makes a real visitor wait. The person who happened to trigger the queue is holding the connection while your backup starts.

The fix

Two steps: stop WordPress triggering cron on page loads, then have the server call it on a real schedule.

Step one. Add this to wp-config.php, above the line that says “That’s all, stop editing”:

define('DISABLE_WP_CRON', true);

This stops the page-load trigger. It does not disable scheduled tasks — the queue still exists and still runs when something calls wp-cron.php. Which is step two, and you should do it in the same sitting, because between the two steps nothing scheduled will run at all.

Step two. Set up a real cron job. Most managed hosts have this in the control panel — look for “Cron Jobs” or “Scheduled Tasks”. Point it at:

https://yoursite.com/wp-cron.php?doing_wp_cron

Every 5 to 15 minutes is right for most sites. If you have shell access, the WP-CLI version is better still, because it skips the HTTP request entirely:

*/10 * * * * cd /path/to/site && wp cron event run --due-now >/dev/null 2>&1

If your host doesn’t offer cron jobs, an external service that pings the URL on a schedule works fine as a fallback.

Checking what’s actually scheduled

Worth doing once, because the contents are usually surprising. With WP-CLI:

wp cron event list

Without shell access, the free WP Crontrol plugin gives you the same view in the dashboard, plus the ability to run an event on demand or delete one.

Things to look for:

  • Orphaned events. Plugins you deleted often leave their scheduled hooks behind. They’ll fire forever, find no function to call, and do nothing — but they’re still in the queue.
  • Events scheduled in the past. A next-run time that’s already gone usually means cron isn’t executing at all.
  • Suspiciously frequent tasks. Anything set to every minute deserves a second look, especially if you don’t recognise it.
  • Duplicates. The same hook registered several times is a sign of a plugin scheduling on every load instead of checking first.

Why this matters beyond scheduled posts

It’s easy to dismiss this as a niche concern until you notice how much depends on it. On a typical site the cron queue is carrying automatic updates and update checks, backups, security scans, cache warming and cleanup, transient expiry, abandoned cart emails, subscription renewals and payment retries, sitemap regeneration, and any monitoring you’ve set up.

If cron is unreliable, all of that is unreliable — and none of it announces its absence. A backup that doesn’t run looks exactly like a backup that ran, right up until you need it.

This is also why Canary Site Monitor’s modules run on staggered WP-Cron schedules rather than on page load: monitoring shouldn’t add weight to a visitor’s request. It’s worth knowing that a site with genuinely broken cron will delay the digest too — if your morning email arrives at odd hours, cron is the first thing to check.

Five minutes of setup buys you a scheduler that actually keeps time. There aren’t many WordPress fixes with that ratio.

Leave a Comment