If your WordPress scheduled posts are publishing hours late, your backup plugin keeps skipping its nightly run, or WooCommerce abandoned-cart emails just stop going out, the cause is almost always the same: WP-Cron isn’t firing reliably. This guide walks through confirming that, finding the real cause, and setting up a fix that actually holds.
What WP-Cron actually is (and why it’s not a real cron job)
Despite the name, WP-Cron isn’t a background service running on a schedule — it’s a script, wp-cron.php, that WordPress checks on every single page load. If it’s been long enough since the last check, WordPress fires off any scheduled tasks that are due. This works fine on a busy site with constant traffic. It breaks down on a low-traffic site, because if nobody visits the site for six hours, nothing due in those six hours runs until someone finally does.
How to tell WP-Cron is actually broken
Look for more than one of these at once:
- Scheduled/future posts publish late, or sit stuck in “Scheduled” status past their publish time
- Backup plugins report a missed or delayed run
- WooCommerce order emails, abandoned-cart emails, or subscription renewals fire late or not at all
- Cached content doesn’t clear on the schedule you configured
- WordPress’s own Tools → Site Health screen flags a scheduled-event problem under the Info tab’s “WordPress Cron” section
Step 1: Confirm it’s actually broken
Check Tools → Site Health → Info → WordPress Cron first — it shows whether WP-Cron is running via the default page-load trigger, whether it’s disabled, and the timestamp of the last successful spawn. If that timestamp is hours or days old on a site that’s getting regular traffic, something is actively blocking it, not just a quiet-traffic gap.
Step 2: Check for blocked loopback requests
Every WP-Cron trigger works by WordPress making an HTTP request back to itself — a “loopback” request. If your host, firewall, or a security plugin blocks the server from calling its own domain, every cron trigger silently fails before it even checks what’s due. Site Health’s “REST API availability” and general loopback checks under the same Info tab will flag this if it’s the cause; if you’re on shared hosting behind a strict WAF or your host has SSL/TLS misconfigured for internal requests, this is the most common single cause of WP-Cron going quiet.
Step 3: Check wp-config.php for conflicting constants
Two constants directly control this behavior and are easy to forget about once a developer sets them and moves on:
define( 'DISABLE_WP_CRON', true );— turns off the page-load trigger entirely. Intentional if you’ve already set up a real server cron job (see Step 5); a problem if it’s set and nothing replaced it.define( 'ALTERNATE_WP_CRON', true );— changes how the loopback request fires, sometimes added as a workaround for a hosting quirk and then forgotten. Can itself cause issues on hosts it wasn’t actually needed for.
Search your wp-config.php for both before assuming the problem is elsewhere.
Step 4: Check for HTTP basic auth blocking wp-cron.php
If your staging site (or a password-protected production site) has HTTP basic authentication enabled at the server level, that login prompt blocks WordPress’s own loopback request to wp-cron.php just as it would block a browser. If you have file access, exclude wp-cron.php specifically from the basic-auth rule rather than disabling protection for the whole site.
Step 5: Set up a real server-side cron job (the permanent fix)
The page-load trigger is a reasonable default, but for anything with real scheduled tasks — WooCommerce, scheduled publishing, nightly backups — a real cron job is worth the five minutes it takes:
- Add
define( 'DISABLE_WP_CRON', true );towp-config.php, above the line that says “That’s all, stop editing.” - In your host’s cPanel/cron manager (or via SSH), add a job that runs every 5-15 minutes. With WP-CLI available:
*/10 * * * * cd /path/to/site && wp cron event run --due-now >/dev/null 2>&1. Without WP-CLI:*/10 * * * * wget -q -O - https://yoursite.com/wp-cron.php?doing_wp_cron >/dev/null 2>&1. - Confirm the job actually ran by checking Site Health’s last-spawn timestamp again after the interval passes.
This is the same background-task pattern covered in more depth in our guide on offloading heavy tasks with asynchronous background processing — worth reading if you’re building a plugin that schedules its own recurring jobs, not just fixing an existing one.
Step 6: Verify scheduled events are actually running
Install WP Crontrol (or a similar cron-inspection plugin) to see the actual list of scheduled hooks, their next-run time, and run them manually to confirm they complete without errors. This is also how you catch a plugin that scheduled a duplicate or runaway recurring event, which shows up as a wp-cron slowdown that looks identical to a broken trigger but has a completely different fix.
When this is a symptom of a bigger hosting problem
If loopback requests are blocked, that’s not only a cron problem — it can affect scheduled backups (see our backup and disaster recovery guide for why a verified backup schedule matters, not just a configured one) and any plugin that depends on WordPress talking to itself. If you’ve worked through all six steps and scheduled tasks are still unreliable, that usually points to a hosting environment that needs a proper look rather than another plugin — this is exactly the kind of thing covered under ongoing WordPress maintenance, where someone is actually watching whether your scheduled tasks ran, not just whether the site loads.


