Most WordPress sites run dozens of background tasks every hour — publishing scheduled posts, processing WooCommerce orders, firing off backup routines, clearing expired cache. Few site owners realize all of it depends on a single mechanism that stops working the moment traffic drops or a caching plugin does its job too well.
That mechanism is WP-Cron, and when it breaks, nothing tells you. No error in the dashboard, no email, no log entry. Scheduled posts stay in draft, backups stop running, WooCommerce order emails pile up unsent. You find out when a customer complains or when you need a restore and discover the last backup is three weeks old.
Below: every reason WP-Cron stops working, how to figure out which one hit you, and how to fix it so it stays fixed.
What WP-Cron does (and why it's unreliable)
WordPress has a built-in pseudo-cron system. It's not a real cron daemon running on a clock. Instead, the entire mechanism hangs on one hook: every time a visitor loads any page, WordPress runs wp_cron() at the end of the request. That function checks a timestamp in the database to see if any scheduled events are overdue. If they are, WordPress spawns a separate background HTTP request to wp-cron.php, which walks through the event queue and executes each overdue callback: sending emails, running backups, clearing caches, whatever plugins have registered.
The chain looks like this: visitor hits a page → WordPress loads → wp_cron() hook fires → checks the cron option in wp_options table → finds overdue events → sends an async HTTP request to wp-cron.php → that request processes the queue independently from the visitor's page load. The visitor never sees any of this; it happens in the background.
Notice the key word in step one: "visitor." No visitors, no hook, no check, no cron. A staging site that gets three hits a day, a new blog with minimal traffic, a membership site behind a login wall — none of them will trigger this chain reliably. Your "daily backup at 3 AM" might not run until someone visits at noon because there was literally nobody to trigger the hook overnight.
This design made sense when WordPress launched in 2003. Shared hosting rarely gave users access to system crontab, and most WordPress sites were personal blogs where a few hours of delay didn't matter. Twenty years later, WordPress powers e-commerce stores processing orders every minute, and the original design shows its age. (For background on how real cron works, see our complete cron job guide.)
The seven reasons WP-Cron stops working
When scheduled tasks stop executing, one of these is almost always the cause.
1. Low or no traffic
The most common reason, and the one most tutorials gloss over. Since the entire cron mechanism depends on that wp_cron() hook firing during a page load, fewer visitors means fewer chances for the hook to run. A site getting ten visits a day has ten chances for cron to trigger. If those visits cluster between 9 AM and 5 PM, nothing scheduled overnight will execute until the first visitor shows up in the morning.
To put it concretely: you schedule a backup for 3 AM. At 3 AM nobody is on the site. At 3:15 AM, still nobody. The backup event sits in the queue, marked overdue, waiting for a page load to trigger the check. At 8:47 AM someone Googles your site, clicks through, and that single page load finally fires wp_cron(). Now the backup runs. Almost six hours late.
2. Full-page caching intercepting requests
Remember the chain: visitor loads a page → WordPress (PHP) executes → wp_cron() hook fires. Caching plugins like WP Super Cache, W3 Total Cache, or LiteSpeed Cache short-circuit this chain. They save a static HTML copy of the page and serve it directly on the next request, without ever loading WordPress or executing PHP. The visitor gets a fast page, but the wp_cron() hook never fires because WordPress never ran.
The same applies to CDNs like Cloudflare or Fastly that cache the entire response at the edge. A visitor in Berlin gets served from a Cloudflare server in Frankfurt — the request never reaches your hosting at all, so WordPress never loads, and cron never triggers.
Some caching plugins are aware of this and exclude wp-cron.php from caching, or include a JavaScript-based cron trigger. But not all of them, and the behavior varies by configuration.
3. DISABLE_WP_CRON set to true without a replacement
This is surprisingly common. A developer or hosting provider adds define('DISABLE_WP_CRON', true); to wp-config.php to stop the on-every-page-load behavior (which is correct), but forgets to set up the actual external trigger. Or they set it up, move hosts, and the server crontab entry doesn't migrate.
Check your wp-config.php first. If that constant is defined, you need an external trigger. No exceptions.
4. Loopback connection failures
Here's something that surprises people who haven't looked under the hood: when WP-Cron fires, WordPress doesn't just call a PHP function directly. It makes an HTTP request to itself, to https://yoursite.com/wp-cron.php, as if it were an external visitor. This request leaves your server, goes out to the internet (resolving your domain through DNS), and comes back to the same server. That round trip is called a "loopback." It exists so that cron processing happens in a separate PHP process and doesn't slow down the visitor's page load.
The problem: several things can break this round trip:
- DNS resolution failing for the site's own domain on the server (common on poorly configured VPS setups)
- Firewall rules blocking the server from connecting to its own IP
- Hosting providers throttling or blocking internal HTTP requests
- Self-signed SSL certificates on staging environments causing cURL to reject the connection
WordPress 5.2+ has a Site Health screen (Tools > Site Health) that tests loopback requests. If it shows a failure there, that's your cron problem too.
5. Security plugins blocking wp-cron.php
Wordfence, Sucuri, iThemes Security, and similar plugins can block the loopback request or block external access to wp-cron.php. The request fires, but gets a 403 or times out. No error appears in the WordPress admin. The cron just silently stops.
6. The cron lock is stuck
WordPress uses a transient called doing_cron to prevent two cron processes from running simultaneously. If a previous cron run crashed mid-execution (a PHP fatal error, a memory limit hit, a timeout), this lock can get stuck. Until it expires or is manually cleared, no new cron events will process. This one is easy to miss because everything else looks perfectly healthy.
7. A plugin registered a broken event
A misbehaving plugin can register a cron event with a callback function that no longer exists (because the plugin was deactivated or updated). When WP-Cron tries to execute this orphaned event, it throws a fatal error and stops processing the rest of the queue. Every other scheduled task sits behind the broken one.
Diagnosing the problem step by step
Before fixing anything, figure out which of the seven causes applies. Guessing leads to layered workarounds that mask the real issue.
Check if DISABLE_WP_CRON is set
Open wp-config.php and search for DISABLE_WP_CRON. If it's set to true and you don't have a system crontab or external service triggering wp-cron.php, that's the entire problem. You can either remove the constant (letting WP-Cron go back to the page-load trigger) or set up a proper external trigger, which is the better long-term fix.
Install WP Crontrol and inspect the queue
The free WP Crontrol plugin (Tools > Cron Events) shows every scheduled event, when it was supposed to run, and whether it's overdue. Things to look for:
- Many overdue events: cron isn't running at all. The trigger mechanism is broken.
- One specific event always overdue, others fine: that event's callback is crashing and blocking the queue.
- Events with "none" as the next run: orphaned events from deactivated plugins. Safe to delete.
Test the loopback connection
Go to Tools > Site Health and look for the loopback request test. If it fails, your server can't make HTTP requests to itself. The fix depends on your hosting: DNS configuration, firewall rules, or server-level cURL settings.
For manual testing via SSH:
curl -v https://yoursite.com/wp-cron.php
If you see a connection refused, SSL error, or 403 response, that's your answer.
Check the cron lock
A stuck transient won't show up in the WordPress admin. You need to check the database directly or use WP-CLI:
wp transient get doing_cron
If this returns a timestamp that's more than a few minutes old, the lock is stuck. Clear it:
wp transient delete doing_cron
Then trigger cron manually and check if events process:
wp cron event run --due-now
Enable cron-specific debug logging
Add these lines to wp-config.php temporarily:
define('WP_DEBUG', true);
define('WP_DEBUG_LOG', true);
define('WP_DEBUG_DISPLAY', false);
Then trigger wp-cron.php manually (visit the URL or use curl) and check wp-content/debug.log. If a plugin callback is throwing fatal errors during cron execution, they'll appear here. This catches the "broken event blocking the queue" scenario that no amount of WP Crontrol staring will reveal.
Fixing it properly (not just patching the symptom)
Your hosting situation determines the best approach, but all start the same way: disable the unreliable page-load trigger and replace it with something clock-based.
If you have SSH access
Add this to your crontab (crontab -e):
*/5 * * * * cd /var/www/yoursite && wp cron event run --due-now --quiet 2>&1 | logger -t wp-cron
Not sure about the */5 * * * * syntax? The cron every 5 minutes guide explains what each field means.
WP-CLI is better than the HTTP approach (no firewall issues, no SSL problems, better error visibility). The logger pipe sends output to syslog so you can check /var/log/syslog if something goes wrong.
Then add to wp-config.php:
define('DISABLE_WP_CRON', true);
If you're on managed WordPress hosting
Check whether your host already handles this. Kinsta runs server-side cron every 15 minutes and disables WP-Cron by default. WP Engine does the same with their own system cron. SiteGround includes a cron manager in their control panel. Cloudways lets you set cron jobs through their dashboard.
If your host already runs server-side cron, adding your own external trigger on top creates duplicate execution — two processes trying to run the same events. That causes the race conditions and duplicate processing you're trying to avoid.
If you have no server access (shared hosting, managed hosting without cron)
An external cron service hits your wp-cron.php URL from outside your infrastructure, on a schedule you define. The trigger is independent of your server, which eliminates the loopback and traffic dependency problems entirely.
Two steps: paste https://yoursite.com/wp-cron.php as the target URL, pick a 5-minute interval. Most WordPress sites don't need anything faster than that.
We covered the full comparison of external cron services (cron-job.org, EasyCron, FastCron, WatchCron) in a separate guide with pricing tables and trade-offs. The short version: free services work but offer no failure alerts. Paid services add monitoring, notifications, and response logging.
What actually breaks when WordPress cron fails
Most articles describe WP-Cron failures in abstract terms. Here's what specifically stops working, because the urgency depends on what your site does:
Scheduled posts. If you write content in advance and schedule it for specific dates, those posts stay in "Scheduled" status until cron runs. A post scheduled for 9 AM Monday might not publish until someone visits the site Tuesday afternoon. For content teams coordinating social media promotion around a publish time, this is actively damaging.
WooCommerce order processing. Pending payment cleanup, subscription renewals, abandoned cart emails, stock status updates, and scheduled sales start/end times all rely on WP-Cron. A broken cron means orders stay in pending status longer than expected, subscription charges miss their window, and time-limited sales don't activate or deactivate on schedule.
Backup plugins. UpdraftPlus, BackWPup, BlogVault, and similar plugins schedule backups through WP-Cron. When cron stops, backups stop. Nobody notices because backup plugins don't have a visible indicator in the admin dashboard — you discover the gap when you actually need a restore and find the last backup is three weeks old.
Security scans. Wordfence and Sucuri schedule periodic malware scans through WP-Cron. No cron, no scans. The irony: a security plugin might be blocking wp-cron.php (cause #5) while simultaneously depending on it for its own scheduled tasks.
Transient and cache cleanup. WordPress accumulates expired transients in the wp_options table. The built-in cleanup runs on cron. Without it, the options table grows indefinitely, slowing down every database query on the site. On high-traffic sites, this manifests as gradually increasing page load times over weeks.
Monitoring so it doesn't silently break again
Here's the gap in every "fix WP-Cron" tutorial: you apply the fix, confirm it works today, and move on. Then a WordPress update changes something, or your hosting migrates servers, or a security plugin adds a new firewall rule, and the cron quietly breaks again. You won't find out until the next symptom surfaces — a missed backup, a stuck scheduled post, an angry WooCommerce customer.
A system crontab entry runs silently. It either works or it doesn't, and it won't tell you which. Even if you redirect output to a log file, nobody checks server logs proactively.
External cron services that combine execution with monitoring close this gap. WatchCron's Cloud Cron for WordPress triggers wp-cron.php on your schedule and logs the HTTP status code, response time, and response body for every execution. If the response changes from 200 to 403 (security plugin started blocking it), or the response time jumps from 200ms to 30 seconds (a cron event is hanging), or the request times out entirely, you get an alert through Slack, Telegram, email, or whichever channel you've configured.
On the free plan you get 3 cloud cron tasks — enough for a single WordPress site running wp-cron.php every 5 minutes. The setup takes about two minutes: add the URL, pick the interval, connect a notification channel. The Starter plan covers agencies and freelancers managing multiple client sites with 75 monitored tasks.
Quick reference: diagnosis flowchart
Start here when WordPress scheduled tasks aren't running:
- Is
DISABLE_WP_CRONset in wp-config.php? → If yes, do you have a server cron or external service set up? If not, that's the problem. - Does Site Health show a loopback failure? → Fix DNS, firewall, or SSL configuration on your server.
- Does WP Crontrol show many overdue events? → Cron trigger is broken. Follow the steps above for your hosting type.
- Does WP Crontrol show one specific event always stuck? → That event's callback is crashing. Enable debug logging, trigger cron, check
debug.log. - Is
doing_crontransient stuck? → Delete it with WP-CLI and re-trigger. - Is a security plugin blocking wp-cron.php? → Check firewall rules. Whitelist your server's own IP or external cron service IPs.
- Everything looks fine but tasks still don't run? → Check server error logs. PHP memory limits or execution time limits can kill cron processes silently.
If none of these solve it, the problem is usually environmental: a reverse proxy stripping headers, a container restart policy that kills long-running PHP processes, or a PHP-FPM pool running out of workers. At that point you're debugging infrastructure, not WordPress.
For the full walkthrough on replacing WP-Cron with an external trigger (including service comparisons and WP-CLI setup), see the complete guide to replacing WP-Cron. If you need help building cron expressions, the visual builder shows you exactly when each schedule fires. And for WordPress sites specifically, Cloud Cron for WordPress handles both the trigger and the monitoring in one setup.