Skip to main content

WP-Cron Not Running: Why WordPress Skips Scheduled Tasks and How to Know

WP-Cron not running means backups, scheduled posts and plugin jobs quietly stop. Find the cause fast, move to a real cron, and get alerted when it stops again.

FLAREWARDEN
FlareWarden Team
8 min read

WP-Cron isn’t a scheduler. It’s a to-do list that WordPress checks when someone loads a page, and if nobody visits, or the check can’t complete, nothing on the list runs. That’s the whole mechanism behind a scheduled post that never published, a backup plugin with a three-week gap, and a newsletter that went out on Thursday instead of Monday.

What “Not Running” Actually Means in WordPress

WordPress’s own documentation is plain about it: WP-Cron checks the list of scheduled tasks on every page load and runs whatever is due during that load. It doesn’t run constantly the way a system cron does. A task scheduled for 2 p.m. on a site with no traffic until 5 p.m. runs at 5 p.m.

When a page load does find something due, WordPress doesn’t run the work inside your visitor’s request. It fires a background request back at its own wp-cron.php, set up in core as a non-blocking call with a 0.01-second timeout (wp-includes/cron.php). That loopback request is where most of the trouble lives, because anything that stops your server from calling itself stops every scheduled task on the site.

So a WP-Cron not running problem is almost always one of two things: nothing triggered the runner, or the runner was triggered and couldn’t get through.

Confirm It Before You Debug It

Two checks tell you in a minute whether you have a real problem.

Site Health. Go to Tools → Site Health. WordPress reports “A scheduled event is late” when an event is past due, and “A scheduled event has failed” once one is more than five minutes overdue (the core thresholds widen to 15 minutes and one hour when DISABLE_WP_CRON is set). It names the event, which is often enough to identify the plugin.

WP-CLI, if your host gives you a shell:

wp cron event list
wp cron test

The first lists every scheduled hook with its next run time, relative to now. A column of events due “2 weeks ago” is your answer. The second tests the spawning system and reports back, and it errors immediately if DISABLE_WP_CRON is set, which is itself a useful finding.

One detail worth knowing about Site Health: its own weekly background check is scheduled with WP-Cron. The warning appears when someone opens that screen. On a site where the runner is broken, nothing will volunteer it.

The Causes, in the Order to Check Them

The WP Crontrol plugin’s help page keeps a good list of reasons events miss their schedule. Here they are, ordered by how quickly each one can be ruled out.

  1. DISABLE_WP_CRON is set and nothing replaced it. Search wp-config.php for it. When it’s true, core skips spawning the runner entirely. That’s the correct setup if a real cron job calls wp-cron.php, and a silent outage if someone set the constant during a migration and never added the cron job.
  2. Low traffic. No page loads, no runner. Staging sites, brochure sites and anything behind a login are the usual victims. Cached pages can make this worse, since a request served entirely from a CDN or a full-page cache may never execute WordPress at all.
  3. The loopback request is blocked. HTTP Basic Auth on the whole site, a firewall or security plugin that rejects requests from the server’s own IP, or a host where the site’s hostname doesn’t resolve from inside the server. Each one makes the background call to wp-cron.php fail. Site Health runs a separate loopback test, so check that result in the same screen.
  4. A fatal error in a scheduled callback. A plugin’s hook throws a fatal error, that run dies, and depending on what was queued behind it, other events miss their slot too.
  5. A long-running event holds the lock. Core uses a lock to stop two runners overlapping, with a default WP_CRON_LOCK_TIMEOUT of 60 seconds. A job that runs for twenty minutes on every trigger turns a busy schedule into a queue that never drains.

If you reach the end of that list without an answer, ask your host whether they manage cron for you. If DISABLE_WP_CRON is set and you didn’t set it, someone else is responsible for calling wp-cron.php, and that someone is where to look next.

The Fix: Stop Depending on Visitors

For any site where scheduled work matters (backups, order emails, subscription renewals, scheduled publishing), our position is simple: turn off visitor-triggered WP-Cron and run it from a real scheduler. WordPress documents exactly how to hook WP-Cron into the system task scheduler.

Add this to wp-config.php:

define( 'DISABLE_WP_CRON', true );

Then add a system cron job, in your host’s control panel or with crontab -e, that requests wp-cron.php on a schedule:

*/5 * * * * wget -q --delete-after https://example.com/wp-cron.php

Every five minutes suits most sites. It makes timing independent of traffic, and it moves the work off your visitors’ page loads.

It does not, however, make the problem go away. You’ve swapped one silent failure point for another: a crontab line that gets lost in a server move, a control-panel cron that the host disables, or a wget that starts receiving a 401 after someone adds password protection. None of those produce an error anyone sees.

Know When It Stops, Instead of Finding Out

Everything above is diagnosis, and it all started with someone noticing a missing backup. A WP-Cron runner that stops produces no symptom at all. An uptime check of your homepage stays green, because the site is serving pages fine. What you need is the opposite direction: the scheduler reports in, and silence becomes the alert.

That’s what a cron monitor does in FlareWarden. You set how often you expect a check-in and a grace period, and you get a ping URL. If the ping doesn’t arrive in time, the monitor goes Late and alerts. The URL is its own credential and accepts a plain GET, so WordPress can call it with the HTTP API it already has.

Watch the runner itself. Drop this into wp-content/mu-plugins/wpcron-heartbeat.php. It schedules an hourly event whose only job is to check in:

<?php
add_action( 'init', function () {
    if ( ! wp_next_scheduled( 'wpcron_heartbeat' ) ) {
        wp_schedule_event( time(), 'hourly', 'wpcron_heartbeat' );
    }
} );

add_action( 'wpcron_heartbeat', function () {
    wp_remote_get( 'https://app.flarewarden.com/ping/YOUR-TOKEN', array( 'timeout' => 10 ) );
} );

Because the ping rides the same runner as every other scheduled task, it goes quiet in every failure mode on the list above: disabled constant, lost crontab, blocked loopback, dead lock. Set the monitor to a one-hour interval with a grace period of at least 15 minutes. If you’re still on visitor-triggered WP-Cron, expect a quiet site to trip it overnight. That’s the monitor telling you something true, not a false alarm.

Watch one job that matters. For a specific task like a backup, find its hook name in wp cron event list and attach a ping after it at a late priority:

add_action( 'your_backup_plugin_hook', function () {
    wp_remote_get( 'https://app.flarewarden.com/ping/YOUR-TOKEN', array( 'timeout' => 10 ) );
}, 99 );

WordPress runs callbacks in priority order, and plugins use the default of 10, so this runs after the plugin’s own callback. If that callback dies with a fatal error, the ping never fires. The honest limit: it proves the job ran to the end of its callback, not that the plugin considers the backup good. A plugin that catches its own failure and returns quietly will still let the ping through.

The same push-based pattern is how we’d monitor Laravel scheduled tasks, and it matters most on the sites you can’t watch by hand. If you look after dozens of client WordPress installs, a missed-backup alert per site is a cheaper habit than a quarterly audit, which is most of what agencies need from monitoring anyway. Store owners get a second benefit: scheduled order and stock tasks are exactly the kind of work that fails without breaking the storefront, the same class of problem as a checkout that returns 200 while broken.


Key Takeaways

  • WP-Cron only runs when a page loads. No visitors, heavy full-page caching, or a blocked loopback request means no scheduled tasks, with no error shown anywhere.
  • Check Site Health and wp cron event list first. They name the overdue events and show how far behind the schedule is.
  • DISABLE_WP_CRON without a replacement is a common silent outage. Set it only when a real cron job calls wp-cron.php.
  • Move important sites to a system cron. Every five minutes is plenty, and it takes timing out of your visitors’ hands.
  • A system cron can fail silently too. Put a heartbeat on the runner so a lost crontab line or a new password wall produces an alert, not a gap you find weeks later.

Want to hear about the backup that didn’t run while it’s still yesterday’s backup? Start monitoring free with FlareWarden — cron and heartbeat monitors on every plan, 15 monitors, no credit card required.