A Scheduled Event Has Failed: The Event Never Ran, It Did Not Crash
Site Health calls an event failed when its run time is more than five minutes in the past. The message names one overdue event, but the cause is almost always WP-Cron not running at all.
"A scheduled event has failed" means a WP-Cron event's scheduled time is more than five minutes in the past and it still has not run — not that its code crashed. Site Health names one hook, such as wp_version_check or wp_scheduled_delete, but that event is rarely the problem. When one event is overdue, the runner that should have picked it up usually is not running at all.
How Site Health decides
The test is WP_Site_Health::get_test_scheduled_events(). It reads the whole cron array and compares each event's timestamp with the current time:
| Situation | Default | With DISABLE_WP_CRON true |
|---|---|---|
| "A scheduled event is late" | overdue by up to 5 minutes | overdue by 15 minutes to 1 hour |
| "A scheduled event has failed" | overdue by more than 5 minutes | overdue by more than 1 hour |
Both results have the status "recommended", not "critical". The message says "The scheduled event, %s, failed to run" and fills in only the first overdue event the test comes across. The cron array is sorted by time, so that is usually the oldest one. Others may be waiting behind it.
If there are no scheduled events at all, you get a different message: "It was not possible to check your scheduled events".
The trap: a crashing event never shows as failed
Before running a recurring event, wp-cron.php reschedules it and removes the current occurrence, and only then fires the hook. If the callback throws a fatal error, the next occurrence is already in the queue with a future timestamp. Site Health sees nothing overdue.
So the test cannot detect an event whose code breaks every time. It detects events that were never started. A failing callback shows up in the PHP error log, not in Site Health. That points the investigation at what starts cron, not at the named event.
There is one way a crash does matter here. wp-cron.php runs every due event in one PHP request. If an event early in the batch runs into max_execution_time or a fatal error, the request dies, and the events after it wait for the next cron run. On a busy site that comes a minute later; on a quiet one they can sit long enough to be reported. A slow or broken event that recurs often keeps doing this.
Find out what is overdue
WP-CLI shows the full picture rather than the one hook Site Health picked:
wp cron event list --fields=hook,next_run_gmt,next_run_relative,recurrence
wp cron test
wp cron test checks whether WordPress can spawn wp-cron.php over HTTP. If many events are listed as overdue and the test fails, the cause is below. If only one hook is overdue, look at that hook's code and the log first.
Why WP-Cron stops running
WP-Cron is not a background process. On a page load, WordPress checks for due events and sends a non-blocking request to wp-cron.php; WordPress Cron explained walks through the details. Every one of these breaks that chain:
DISABLE_WP_CRONis true and no system cron replaces it. This is the most common cause. Somebody disabled the visit-triggered cron for performance and never added the real job — or the server was migrated and the crontab was not. Disabling WP-Cron shows both halves.- The loopback request is blocked. A firewall, a security rule that blocks requests to
wp-cron.php, HTTP Basic Auth on a staging site, or broken DNS on the server. Site Health usually shows a loopback request failure next to the cron warning in this case. - Low traffic. No visits, no cron. Events wait until the next page load.
- Caching hides the visits. When every visitor gets a page from a CDN or a static cache, PHP is never executed, so nothing triggers cron, even on a busy site.
- One event uses up the request. A backup or an import that runs past the PHP time limit kills the batch and delays everything behind it, as described above.
Events left behind by removed plugins
The event list often contains hooks from plugins that are long gone. They are untidy but harmless to the test: when WP-Cron reaches such an event, it removes or reschedules it as usual, then fires a hook nobody listens to, which does nothing. They do not stay overdue and do not cause this warning.
They do add up, though. A recurring orphan is rescheduled forever and keeps its place in the cron option, which is autoloaded on every request. Once cron is running again, remove the ones you can identify:
wp cron event delete old_plugin_daily_sync
Check the hook name against your active plugins first. Deleting an event that belongs to an active plugin only lasts until that plugin schedules it again, usually on its next load.
Fix it
For anything beyond a hobby site, replace visit-triggered cron with a system job. Add this to wp-config.php:
define( 'DISABLE_WP_CRON', true );
Then schedule the runner from the server, every five minutes for example:
*/5 * * * * cd /var/www/example.com && wp cron event run --due-now --quiet
If WP-CLI is not available, a request to https://example.com/wp-cron.php?doing_wp_cron from the host's cron panel works too. Remember the wider window afterwards: with DISABLE_WP_CRON set, Site Health only warns after 15 minutes, so a five-minute schedule keeps it quiet.
To catch up immediately after fixing the cause:
wp cron event run --due-now
Common mistakes
- Deleting the named event, which removes a task that is still needed.
- Setting
DISABLE_WP_CRONand forgetting the system job, which turns a late cron into no cron. - Assuming the named hook's code is broken. A crashing recurring event never appears in this test.
- Pointing a system cron at
wp-cron.phpon a URL that redirects or needs a login.
Verify
wp cron event listshows nonext_run_gmtin the past, or only seconds in the past.- Tools › Site Health › Status shows "Scheduled events are running" after a reload.
- A post scheduled two minutes ahead publishes within one cron interval of its time.
wp cron testreports success, if you kept visit-triggered cron.
Frequently asked
- Only how far overdue the event is. By default an event overdue by up to five minutes is late and one overdue by more than five minutes has failed. With DISABLE_WP_CRON set to true the windows widen: 15 minutes to an hour is late, more than an hour has failed.
- No. The named event is a symptom: it is simply the overdue event the test found first. Deleting it removes a task a plugin or core still needs, and the next overdue event takes its place in the message.
- WP-Cron runs only when somebody loads a page. On a quiet site, events pile up between visits, run when traffic arrives, and pile up again. Site Health catches whichever state it happens to see. A real system cron removes the gaps.