WordPress Cron Is Unreliable. Here Is What That Breaks
WordPress Cron is not running reliably on your site because it was never designed to be a real cron daemon. WP-Cron fires only when someone visits your site, which means a site that goes two hours without a visitor will miss every scheduled task during that window. Scheduled posts, backup jobs, email queues, delayed automations, and expiring memberships all depend on this mechanism, and all of them fail silently when it misfires.
This article explains exactly how WP-Cron works, what breaks when it does not fire on time, and how to replace it with a real server-side cron job that runs on a fixed schedule. We also name a genuine limitation this creates for our own automation plugin, Krom Automation, because it is relevant and you deserve to know before you build anything that depends on delays.
If you have ever opened your site in the morning to find a scheduled post still sitting as a draft, a backup that ran three hours late, or a welcome email that landed in a user’s inbox long after they had already given up waiting, WP-Cron is almost certainly the reason.
How WP-Cron Actually Works
Every WordPress page load calls wp-cron.php via a loopback HTTP request. WordPress checks whether any scheduled tasks are overdue, and if they are, it runs them during that request.
The critical word is “overdue.” WP-Cron does not run tasks at the moment they are scheduled. It runs them the next time a page loads after the scheduled time has passed.
On a site that receives a page view every few minutes, this approximation is usually close enough. On a site that goes dark for two hours overnight, every task scheduled during those two hours will be late by up to two hours. On a very low-traffic site, “a few visitors per day” can mean scheduled tasks are late by 6 to 12 hours or more.
WP-Cron does not run tasks at the moment they are scheduled. It runs them the next time someone visits your site after that moment has passed.
There is also a rate-limiting mechanism inside spawn_cron(). WordPress will not spawn a new cron process if one is already running, and it will not spawn one if the last cron run was less than 60 seconds ago. On a very busy site, rapid page loads can suppress cron spawns repeatedly, causing a queue of overdue tasks to build up behind the rate limiter.
What the ?doing_wp_cron URL Means
If you see ?doing_wp_cron=1 appearing in your analytics reports, that is not a bug or a bot. It is WordPress making a loopback HTTP request to itself to execute scheduled tasks.
The request is intentional. The reason it appears in analytics is that your analytics script fires before WordPress can detect the cron request and stop tracking it.
Seeing this URL frequently means WP-Cron is at least running. Seeing it only occasionally, or not at all, is a signal that page traffic is too low to keep it reliable.
What Breaks When WP-Cron Is Unreliable
The list of WordPress features that depend on WP-Cron is longer than most site owners realise. These are the most commonly affected:
- Scheduled posts: A post set to publish at 9:00am may not appear until the first visitor arrives after that time, which could be 9:47am or later on a low-traffic site. Backup plugins: UpdraftPlus, BackupBuddy and similar tools schedule their jobs via WP-Cron. A backup set for 2:00am may not run until 4:00am if traffic is low overnight.
- Email queues: Plugins that queue and batch outgoing emails, including WooCommerce transactional emails processed via a queue, can fall behind or miss sends entirely.
- Membership and subscription expiry: MemberPress, WooCommerce Subscriptions, and similar plugins use scheduled tasks to expire access, send renewal reminders, and trigger payment retries. Late execution means users retain access they should have lost, or miss renewal emails.
- Delayed automations: Any workflow that uses a delay step, such as sending a follow-up email 48 hours after a user registers, fires late if the cron job does not run at the scheduled time.
- Cache clearing and maintenance tasks: Scheduled cache purges, database cleanups, and transient expiry all depend on WP-Cron firing reliably.
How to Check Whether WP-Cron Is Running on Your Site
There are three ways to verify whether WP-Cron is actually executing. Work through them in this order, because the first one answers the question fastest.
- Install WP Crontrol: This free plugin shows every scheduled event, when it last ran, when it is due to run next, and whether it is overdue. An overdue event by more than a few minutes confirms the problem. This is the fastest diagnostic on a live site.
- Check for the
DISABLE_WP_CRONconstant: Open yourwp-config.phpfile and search forDISABLE_WP_CRON. If it is set totrueand you have not yet configured a server-side replacement, your site has no cron execution at all. Tasks are scheduled but never run. - Check for loopback failures: Go to Tools Site Health in your WordPress admin. Look for a loopback request test result. A failing loopback test means WordPress cannot make HTTP requests to itself, which means WP-Cron cannot spawn at all, even when traffic is present. Basic HTTP authentication on the admin, a misconfigured firewall, or a maintenance mode plugin can all block loopback requests.
The Fix: Disable WP-Cron and Replace It With a Real Cron Job
The correct solution is to stop WP-Cron from firing on page loads and replace it with a proper server-level cron job that runs on a fixed interval. This is a two-step change.
Step 1: Disable the traffic-dependent trigger. Add this line to your wp-config.php file, above the line that reads /* That's all, stop editing! */:
define('DISABLE_WP_CRON', true);
This stops WordPress from attempting to spawn cron on every page load. Tasks will now wait until something actively calls wp-cron.php.
Step 2: Set up a real cron job. The command to add to your server’s crontab depends on how you want to call WordPress cron. There are three options:
- WP-CLI (recommended):
*/5 * * * * cd /path/to/wordpress && wp cron event run --due-now --allow-root - Direct PHP execution:
*/5 * * * * php /path/to/wordpress/wp-cron.php - HTTP request via curl:
*/5 * * * * curl -s https://yoursite.com/wp-cron.php?doing_wp_cron /dev/null 2>&1
Running the job every 5 minutes is the standard recommendation for most sites. Every minute is worth considering if you have time-sensitive automations, payment retries, or membership expiry logic that must be precise to within a few minutes.
If Your Host Does Not Support Cron Jobs
Some shared hosting plans do not expose crontab access. In that case, an external ping service is the next best option. Services like cron-job.org and UptimeRobot can make an HTTP request to https://yoursite.com/wp-cron.php?doing_wp_cron=1 on a schedule you define.
Set the interval to every 5 minutes. This is not as clean as a server-side job because it still relies on an HTTP loopback, but it is vastly more reliable than traffic-dependent triggering.
One caveat: if your server blocks external HTTP access to wp-cron.php, the external ping will fail silently. Confirm the URL is accessible from outside your server before relying on this approach.
Running cron every 5 minutes is the standard recommendation. Every minute is worth it if you have payment retries or membership expiry logic that must be precise.
The Race Condition Problem Nobody Talks About
Most articles about WP-Cron cover the traffic-dependency problem and stop there. There is a second problem that affects high-traffic sites and multisite installations: concurrent cron execution and duplicate job runs.
On a busy site, multiple page loads can arrive within the same second. Each one checks whether cron needs to run. WordPress uses a transient-based lock to prevent duplicate spawns, but the lock check and the lock set are not atomic.
Under high concurrency, two processes can both pass the lock check before either sets the lock, and both will proceed to run cron simultaneously. This means the same task can execute twice in rapid succession.
The practical consequences depend on what the task does:
- Idempotent tasks (running twice produces the same result as running once) are safe. Cache clearing and database cleanups fall into this category.
- Non-idempotent tasks (running twice causes real harm) are not safe. Sending a transactional email twice, charging a payment method twice, or creating duplicate records are all examples of non-idempotent execution.
- Multisite environments add another layer because each site in the network has its own scheduled events, but all sites share the same server resources. A large network with many sites can generate substantial cron load even when individual sites are low-traffic.
The solution at the application level is to build tasks that are safe to run twice. Check whether the work was already done before doing it again.
Krom Automation handles this with its run once per entity enforcement, which prevents a workflow from executing more than once for the same triggering object even if cron fires the task multiple times. For plugins you do not control, the only mitigation is to replace WP-Cron with a proper server-side job (which reduces but does not eliminate the concurrency window) and to monitor execution logs for duplicates.
An Honest Note About Krom Automation and Delays
Krom Automation uses delay scheduling to postpone actions by minutes, hours, days, or weeks. You can configure a workflow to send a follow-up email 3 days after a user registers, or to change a post status 24 hours after it publishes. These delays are real and they work as described.
However, the execution of those delayed actions runs through Action Scheduler, which itself depends on WP-Cron to fire. If WP-Cron is unreliable on your site, delayed workflow actions will also be unreliable. A 48-hour delay configured in Krom Automation means “at least 48 hours after the trigger, executed the next time WP-Cron fires after that point.” On a low-traffic site without a server cron, that could be 49 hours, or 52 hours.
This is not a bug in Krom Automation. Action Scheduler is a robust queue system used by WooCommerce itself, and it handles failure, retry, and logging reliably.
But it cannot escape the underlying constraint that WordPress cron must fire to process the queue. If you use Krom Automation for anything time-sensitive, configure a real server cron job first. The Schedule Trigger documentation covers the interaction between server cron and scheduled workflows in more detail.
The same applies to any other plugin that uses delayed or scheduled actions: WooCommerce Subscriptions, UpdraftPlus, MemberPress, email marketing queues. They all sit on top of the same WP-Cron layer. Fix the layer and everything built on it becomes more reliable.
Action Scheduler is robust. But it cannot escape the constraint that WP-Cron must fire to process the queue. Fix the cron layer first, before building anything time-sensitive on top of it.
Will Disabling WP-Cron Break Your Site?
No, as long as you have configured a replacement before setting DISABLE_WP_CRON to true. The constant disables the traffic-triggered spawning mechanism, not the scheduled task system itself.
Every event registered with wp_schedule_event() stays registered. WordPress will continue to pick up and run those events when something calls wp-cron.php directly, whether that is your server cron, a WP-CLI command, or an external ping service.
The one scenario that breaks things: you set the constant, forget to configure a replacement, and nothing calls wp-cron.php anymore. Every scheduled task then piles up in the queue and runs in a burst the next time something does call it. Prevent this by confirming your server cron is working within 10 minutes of making the config change.
Decision Table: Which Fix Applies to Your Situation
| Your situation | Most likely cause | Fix to apply first |
|---|---|---|
| Scheduled posts publish late on a low-traffic site | Traffic-dependent cron, no visitors during the window | Add server cron job running every 5 minutes |
| No tasks running at all, even on a busy site | DISABLE_WP_CRON set to true with no replacement |
Add server cron job, or remove the constant |
| Tasks overdue but traffic is present | Loopback request failure blocking cron spawn | Fix loopback access (check Site Health, remove HTTP auth) |
| Shared hosting, no crontab access | No server-level cron available | Use cron-job.org or UptimeRobot to ping wp-cron.php every 5 minutes |
| Duplicate emails or records on a high-traffic site | Concurrent cron spawns, non-idempotent tasks | Replace WP-Cron with server job, add idempotency checks to tasks |
| Delayed automations arriving late | Action Scheduler queue not being processed on time | Server cron every 1 to 5 minutes, verify with WP Crontrol |
Cost of Getting This Wrong
| Task type | Consequence of late execution | Estimated real cost |
|---|---|---|
| WooCommerce subscription renewal email | Customer renews late or churns | Revenue loss per churned subscriber |
| Welcome email after registration | First impression damaged, engagement drop | Lower activation rate on the cohort |
| Overnight backup job | Backup runs 2 to 4 hours late, or skips | Data exposure window if site is compromised overnight |
| Payment retry for failed subscription | Retry fires late, subscriber has already cancelled | Lost renewal, 15 to 30 minutes of manual recovery per case |
| Scheduled post | Post goes live hours after the intended slot | Missed social media window, wasted scheduling effort |
How Krom Automation Handles the Cron Problem
Krom Automation runs all workflow executions in the background via Action Scheduler rather than during page loads. This means no visitor ever waits for a workflow to complete, and heavy tasks like HTTP requests or AI generation do not block the site. The execution logging captures every step, every output, and every failure, so you can see exactly what ran and when.
The failure notification system sends an email alert when a workflow fails, and automatic retry with configurable backoff means transient failures (a slow API, a brief network timeout) do not require manual intervention. You can review the full execution history in the analytics dashboard, which shows success rate per workflow, not just a total count. That matters because a workflow failing 20% of the time looks identical to a healthy one until you check the per-workflow breakdown.
If you want to see how delays interact with your specific setup, the workflow simulator lets you run a dry test with no side effects before deploying anything to production. And the conditions and branching documentation covers how to build logic that handles edge cases, including late-firing triggers, without creating duplicate outcomes.
If you are building automations that touch WooCommerce Subscriptions or MemberPress membership events, fixing cron first is not optional. Those integrations fire triggers on expiry and renewal events that depend entirely on WP-Cron processing the queue on time.
Browse the full Krom Automation feature list to see how the execution layer is built.
The Verdict
WP-Cron is a reasonable approximation for simple sites with steady traffic. It is a genuine problem for any site where tasks must execute within a defined window, which includes every site running automations, handling subscription payments, sending time-sensitive emails, or running scheduled backups.
The fix takes about 10 minutes: add one line to wp-config.php and configure a server cron job to call WP-CLI every 5 minutes. If your host does not support cron jobs, set up a free external ping on cron-job.org. Either approach converts an unreliable approximation into a system you can trust.
If you are building automated workflows on top of WordPress and want a plugin that runs executions in the background, logs every step, and handles retries without manual intervention, download Krom Automation free from the WordPress.org plugin directory and configure your server cron before you build your first workflow. In that order.
See Krom Automation pricing and plan details if you want the Pro features, including the Schedule Trigger, incoming webhook receiver, and 60+ additional actions.
Frequently Asked Questions
Can WP-Cron run tasks at an exact time?
No. WP-Cron runs tasks the next time a page loads after the scheduled time. On a busy site the delay is seconds.
On a low-traffic site it can be hours. A real server cron job running every 5 minutes reduces the maximum delay to 5 minutes, which is as close to exact as WordPress supports natively.
My hosting plan does not support cron jobs. What can I do?
Use an external ping service such as cron-job.org or UptimeRobot. Configure them to make an HTTP GET request to https://yoursite.com/wp-cron.php?doing_wp_cron=1 every 5 minutes. Confirm the URL is reachable from outside your server before relying on this as your only mechanism.
How often should I run the server cron for WordPress?
Every 5 minutes is the standard recommendation and sufficient for most sites. Every 1 minute is worth configuring if you process subscription payment retries, time-sensitive membership expiry, or delayed automations where precision within a few minutes matters.
Why is my backup plugin missing scheduled backups?
Backup plugins schedule their jobs via WP-Cron. If WP-Cron does not fire at the scheduled time, the backup simply does not run.
No error is raised and no alert is sent by default. Check WP Crontrol for overdue events and configure a server cron job to guarantee the backup window is hit.
Will disabling WP-Cron break any plugins?
No, as long as you configure a server cron replacement immediately. Setting DISABLE_WP_CRON to true stops the traffic-triggered spawning but leaves all scheduled events in place.
They will run normally the next time something calls wp-cron.php directly. The only risk is setting the constant without a replacement, which leaves tasks queued indefinitely.