WordPress

WP-Cron Not Running: Why It Fails and the Server Cron Fix

Scheduled posts stuck, backups skipped, emails delayed: WP-Cron failing on managed hosting. Why it stops, how to diagnose it, and the server cron that fixes it.

5 min read
WordPressHostingDebugging
907 words5 min read

The symptoms

A post scheduled for 9am is still "Scheduled" at noon, with the dreaded "Missed schedule" label. The backup plugin's nightly job has not run for a week. WooCommerce's follow-up emails arrive days late or never. The sitemap is stale. Everything that was supposed to happen on a timer is not happening, and nothing in the admin says why.

These are all the same fault: WP-Cron is not running, or not running often enough.

How WP-Cron actually works

WordPress has no background process. It cannot; it is PHP that runs when a request arrives and stops when the response is sent. So its scheduler, WP-Cron, is a trick: on every page load, WordPress checks whether any scheduled task is overdue and, if so, fires a request to wp-cron.php on itself to run it. The task runs inside that spawned request.

This means WP-Cron depends on three things: someone loading a page, the site being able to make an HTTP request to itself, and that request completing. Break any of them and scheduled tasks silently stop.

Why it fails

Nobody visits. Low-traffic sites, staging sites, sites behind a login. No page load, no cron. A post scheduled for 6am on a site that gets its first visitor at 9am publishes at 9am.

Page caching serves everything. A full-page cache at the host or CDN returns pages without running PHP. WordPress never gets to check the schedule. Sites with excellent caching are the most likely to have broken cron, which is why it is so common on managed hosting.

The site cannot reach itself. The spawned request to wp-cron.php fails because of a firewall rule, basic auth on staging, a DNS quirk where the server cannot resolve its own domain, an SSL certificate the server does not trust, or a security plugin blocking requests with the cron user-agent. WordPress does not surface this failure anywhere visible.

The cron request is blocked or slow. A host that blocks loopback requests. A plugin that hooks cron with a task that takes minutes and times out, taking the whole run with it. ALTERNATE_WP_CRON hacks left over from a previous fix.

DISABLE_WP_CRON is set without a replacement. Someone disabled the page-load trigger, intending to set up a server cron, and did not.

Locked runs. WP-Cron uses a transient lock to avoid concurrent runs. A run that died mid-way can leave a stale lock that blocks the next run for a period.

Diagnosing it

Install WP Crontrol. It lists every scheduled event, its next run time and whether it is overdue. A screen full of past-due events confirms the problem. It also shows any cron-related errors, such as an inability to spawn the cron request, at the top of the page.

Test the loopback. Site Health (Tools, Site Health) reports "The REST API encountered an error" or "Your site could not complete a loopback request" when the server cannot reach itself. That message is WP-Cron's cause too.

Check wp-config.php for DISABLE_WP_CRON and ALTERNATE_WP_CRON.

Run it manually. With WP-CLI: wp cron event list shows the schedule; wp cron event run --due-now runs everything overdue. If the tasks work when run this way, the tasks are fine and the trigger is the problem.

Check for a monster task. In Crontrol, look for events from plugins doing heavy work: imports, image regeneration, large exports. A task that times out can kill the run.

The fix: a real cron job

Stop relying on page loads. Disable the page-load trigger and have the server run cron on a real schedule.

Step 1. In wp-config.php:

PHP
define( 'DISABLE_WP_CRON', true );

Step 2. Add a system cron entry that runs every minute. Two options:

Via WP-CLI, which is the cleanest, because it runs PHP directly with no HTTP request involved and so cannot be blocked by anything web-facing:

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

Or via HTTP, if WP-CLI is unavailable:

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

Every minute is correct. WP-Cron itself only runs tasks that are due, so a minute-by-minute trigger costs almost nothing and gives scheduled posts and emails minute-level accuracy.

Step 3. On managed hosts, use their mechanism. Kinsta, WP Engine, Cloudways, SiteGround and others have a "server cron" or "scheduled tasks" option that does this for you, and some enable it by default. Check whether yours does; if it does, the page-load trigger may already be redundant.

Step 4. Verify. After a few minutes, WP Crontrol should show no overdue events and the next-run times ticking forward. Schedule a test post for two minutes ahead and watch it publish on time.

For multisite and multiple sites

One cron entry per site, or a small script that loops through them with wp --url=. On multisite, wp cron event run --due-now per site URL, since each site has its own schedule.

Keep an eye on it

A broken cron is silent, which is the whole problem. On sites we maintain, the monthly check includes glancing at Crontrol for overdue events and confirming the server cron is still present, because host migrations and control panel changes have a way of dropping it. It is one of the care plan items nobody notices until a backup is missing, and setting it up properly is standard on every WordPress site we configure.

All writingHire me for this