Sunday, July 5, 2026
All guides
Troubleshooting guide Scheduling

WP-Cron Not Running? How to Fix It and Replace It With a Real Cron Job

Scheduled posts stuck as Missed Schedule, backup plugins that never fire, WooCommerce emails that show up hours late — the culprit is almost always wp-cron. Here is what wp-cron actually does, why it fails, and how I move client sites onto a real server cron in about ten minutes.

Arjun Mehta Published July 5, 2026 Last reviewed July 5, 2026 13 min read Step-by-step walkthrough
Reviewed and tested by the WPRescue team on a real WordPress install before publishing. How we test fixes
WP Crontrol showing WordPress cron events and next run schedule

What's Happening

Something on the site is supposed to run on a schedule and it never does. A scheduled post publishes hours late or lands on Missed Schedule. UpdraftPlus or BackWPup skips a nightly backup. WooCommerce fails to send the follow-up email six hours after checkout. The Action Scheduler queue climbs into the thousands. Nine times out of ten the shared cause is wp-cron: it is not a real cron job, it only runs when someone visits the site, and on a low-traffic or aggressively cached site it barely runs at all.

Wp-cron is one of the most misunderstood pieces of WordPress. It has cron in the name, which makes people assume it works like a Unix cron job running quietly in the background. It does not. Wp-cron is a PHP script that only executes when a visitor loads a page on your site, and even then only when WordPress decides enough time has passed since the last run.

That design was a smart choice back when most hosts did not give users cron access. In 2026, on a site with full-page caching, a low traffic pattern, or a serious plugin stack that queues hundreds of Action Scheduler tasks, it is the wrong choice. Scheduled posts miss their window. Backup plugins skip runs. WooCommerce follow-up emails arrive hours late.

The fix is not to install another cron plugin on top of wp-cron. The fix is to turn wp-cron off inside WordPress and let the server run cron the way every other serious application does. This guide walks through what wp-cron actually is, how to prove it is the problem on your site, and the exact steps to replace it on cPanel, Plesk, and a plain Linux server.

What wp-cron actually does

Every time WordPress needs to run something on a schedule, whether that is publishing a scheduled post, sending a WooCommerce email, running a backup, or clearing a transient, it registers an event with wp_schedule_event(). Those events are stored in a serialised array inside the cron row of the wp_options table.

When a page loads, WordPress checks that array, sees which events are due, and fires them by making a non-blocking HTTP request to wp-cron.php on its own domain. That request is what actually runs the queued tasks. If no page loads, no request fires, no tasks run.

The knock-on effects are subtle. A cached page never hits PHP, so a visitor who lands on a cached page does not trigger wp-cron. On a heavily cached site the only requests that trigger wp-cron are admin loads and cache misses, which can be as few as a handful per day on a small business site. That is why the problem is worse the better your caching gets.

The symptoms that point to wp-cron

You almost never see an error that says wp-cron is broken. You see the downstream effect. Here is the pattern I look for when a client says something is late or missing.

  • Scheduled posts stuck on Missed Schedule for hours or days
  • UpdraftPlus, BackWPup, or BlogVault reporting skipped or delayed backup runs
  • WooCommerce order confirmation emails arriving 30 to 60 minutes late
  • Action Scheduler queue in WooCommerce > Status > Scheduled Actions climbing to thousands of pending rows
  • MailPoet campaigns sitting in Sending status without progressing
  • Broken Link Checker or Redirection plugin logs stale for weeks
  • Site health warning that a scheduled event failed to run

Prove it with WP Crontrol before you change anything

Do not disable wp-cron on a hunch. Install WP Crontrol, a free plugin by the John Blackbourn who maintains Query Monitor, and go to Tools > Cron Events. You see every scheduled event, when it was last run, and when it is due to run next.

If Next Run is a date in the past for many events and the Last Run column shows they never actually fired, wp-cron is not firing often enough. That is your confirmation. If everything is on time and Next Run is always in the future, the problem is somewhere else and disabling wp-cron will not help.

WP Crontrol plugin showing WordPress scheduled cron events with next run times
WP Crontrol lists every scheduled event, when it last fired, and when the next run is due. This is the fastest way to confirm wp-cron is the bottleneck.

Step 1: Disable wp-cron in wp-config.php

Open wp-config.php through your host's file manager, an FTP client, or WP-CLI. Above the line that reads 'That's all, stop editing! Happy publishing.' add a single line. This does not delete wp-cron.php, it just tells WordPress to stop triggering it from page loads.

phpAdd above the 'That's all, stop editing' comment in wp-config.php. Save the file.
// Disable the visitor-triggered WordPress cron.
// Replace with a real server cron that hits wp-cron.php every 5 minutes.
define( 'DISABLE_WP_CRON', true );

Step 2: Add a real server cron on cPanel hosting

Most shared hosts (Bluehost, SiteGround, Hostinger, Namecheap, A2, GreenGeeks) expose cron through cPanel. Log in, scroll to the Advanced section, and click Cron Jobs. In the Add New Cron Job area, choose Common Settings > Every 5 minutes. That fills in the schedule fields as */5 * * * *.

In the Command box, paste the line below and replace yourdomain.com with your actual domain. The --spider flag makes wget request the URL without saving the response, and the -q flag silences output so the server does not email you every 5 minutes. Save. Within 10 minutes you should see WP Crontrol events firing on their new schedule.

bashPaste into cPanel > Cron Jobs > Command. Schedule: Every 5 minutes. Replace yourdomain.com with your real domain.
wget -q -O - https://yourdomain.com/wp-cron.php?doing_wp_cron >/dev/null 2>&1

Step 3: Add a server cron on Plesk, DirectAdmin, or a raw VPS

On Plesk, go to Websites & Domains > Scheduled Tasks > Add Task. Set task type to Fetch a URL, enter the wp-cron URL, and set Run to every 5 minutes. On DirectAdmin, use the Cron Jobs section and paste the same wget command as above.

On a plain Linux VPS with SSH access, run crontab -e as the site's user (not root) and add the line below. Save and exit. Confirm with crontab -l that the entry is there. That is it. WordPress cron now runs on a real schedule.

bashPaste into crontab -e on a VPS. Runs wp-cron every 5 minutes, silently.
*/5 * * * * wget -q -O - https://yourdomain.com/wp-cron.php?doing_wp_cron >/dev/null 2>&1

Step 4: Verify the switch worked

Go back to Tools > Cron Events in WP Crontrol. Watch the Next Run column for the next 15 minutes. Events that were stuck in the past should update to a future timestamp within one cron interval. If they do, the server cron is firing and WordPress is picking up the run.

If nothing changes after 20 minutes, three things are worth checking. First, the URL in the cron command should be the exact URL of your site including https and the correct www or non-www version. Second, if your site sits behind Cloudflare or a WAF, make sure the server can request its own domain (some setups block loopback requests, in which case use the local file path with php /home/user/public_html/wp-cron.php instead). Third, check your host's cron log for errors on the job you just created.

Clearing a stuck DOING_WP_CRON lock

One edge case worth knowing. WordPress writes a transient called doing_cron when a cron run starts and clears it when the run finishes. If a run crashes half way through (memory limit, plugin fatal, timeout), the transient sits there for up to an hour and blocks all further runs. On a busy site this shows up as a sudden multi-hour freeze of every scheduled task.

The fix is a one-line WP-CLI command run over SSH. If you do not have WP-CLI, run the SQL below in phpMyAdmin. Either way the lock clears instantly and the next cron run picks up all the queued events at once.

bashClear a stuck cron lock. Safe to run at any time; the transient regenerates on the next cron start.
# With WP-CLI
wp transient delete doing_cron

# Or with SQL in phpMyAdmin
DELETE FROM wp_options WHERE option_name = '_transient_doing_cron' OR option_name = '_transient_timeout_doing_cron';

A real client example

A B2B services company on SiteGround GrowBig came to me because their sales team was losing leads. WPForms was set to email the sales inbox when someone filled the request-a-quote form. The form worked, entries were captured in the database, but the notification emails were arriving anywhere from 30 minutes to 4 hours late. Sales was calling leads back a day after they filled the form and losing them to competitors.

WPForms uses Action Scheduler under the hood, and Action Scheduler relies on wp-cron. On a site with LiteSpeed Cache serving 95 percent of requests from cache, wp-cron was only firing when someone hit an admin page. Disabling wp-cron in wp-config and adding a 5-minute cron job in cPanel took ten minutes. The Action Scheduler queue drained in under an hour and notification lag dropped to under 5 minutes. Sales stopped complaining.

When not to touch wp-cron

Managed hosts already do this for you. WP Engine, Kinsta, Flywheel, Pressable, Rocket.net, and Cloudways all disable wp-cron out of the box and replace it with a real system cron that fires every 60 seconds. If you are on any of those, do not add DISABLE_WP_CRON, do not add a cPanel cron (they do not even expose cPanel), and do not install a cron plugin. Doing so can double-fire events and confuse Action Scheduler.

The one exception is if you see the same missed-schedule symptoms on managed hosting. In that case open a support ticket, do not try to fix cron yourself. The problem is almost always something the host needs to look at on their side (a failed cron worker, a queue backlog, a firewall rule blocking their internal cron trigger).

Final checklist

Run through this before you close the tab and consider the job done.

  • WP Crontrol installed and Tools > Cron Events showing events firing on schedule
  • DISABLE_WP_CRON set to true in wp-config.php (only if you are on shared or self-managed hosting)
  • A server cron job hitting wp-cron.php every 5 minutes exists in cPanel, Plesk, DirectAdmin, or crontab
  • The cron URL in the command matches your site's canonical URL exactly (https, correct www/non-www)
  • You are not on a managed host that already handles cron
  • You know how to clear the doing_cron transient if a run ever gets stuck
  • Scheduled posts publish on time, backup emails arrive on schedule, WooCommerce order emails arrive within minutes

Complete Fix Checklist

  1. 1Confirm the problem is wp-cron by installing WP Crontrol and checking whether events are queued but never firing.
  2. 2Add define('DISABLE_WP_CRON', true); to wp-config.php to stop the visitor-triggered version.
  3. 3Set a real server cron (cPanel Cron Jobs, Plesk Scheduled Tasks, or crontab -e on a VPS) to hit wp-cron.php every 5 minutes with wget or curl.
  4. 4Verify the switch by watching the WP Crontrol events run on schedule and by checking your host's cron log.
  5. 5Keep DOING_WP_CRON stuck-lock issues away by deleting the transient with WP-CLI or the database if it ever wedges.

Quick Tips

  • Never delete wp-cron.php from the filesystem, disable it in wp-config instead
  • The 5-minute interval is a good default, tighter than 1 minute stresses shared hosting and adds no real value
  • Managed hosts (WP Engine, Kinsta, Pressable, Cloudways) already replace wp-cron for you, do not add a second cron on top

Frequently Asked Questions

What is wp-cron and how is it different from a real cron job?
wp-cron is WordPress's own scheduler. It lives in wp-cron.php and runs whenever a visitor loads a page. A real cron job is a scheduled task run by the server operating system on a fixed interval, whether anyone visits your site or not. On a low-traffic or heavily cached site, wp-cron barely fires, which is why scheduled tasks silently miss their window.
Is it safe to disable wp-cron?
Yes, as long as you replace it with a real server cron that hits wp-cron.php on a schedule. Disabling wp-cron without a replacement means nothing scheduled runs. The moment you add a cron job in cPanel, Plesk, or crontab that pings wp-cron.php every 5 or 15 minutes, WordPress goes back to running scheduled events, only now on a reliable interval.
How do I know if wp-cron is actually broken on my site?
Install WP Crontrol (free, over 200,000 installs). Go to Tools > Cron Events. If you see events with a Next Run in the past that never move, wp-cron is either disabled or not being triggered often enough. That is the smoking gun. If everything shows a Next Run in the future and firing on time, wp-cron is working.
My scheduled posts show Missed Schedule. Is that always wp-cron?
Almost always. Other causes exist (a bad timezone in Settings > General, a broken database transient, or a plugin that hijacks the publish flow), but on 8 out of 10 client sites I look at with a Missed Schedule pattern, the fix is disabling wp-cron and setting a real server cron. If replacing cron does not fix it, look next at the site timezone and the wp_options row for cron.
What is DOING_WP_CRON and why does it get stuck?
When wp-cron starts a run, WordPress writes a lock (a transient called doing_cron) so two visitors cannot start two cron runs at once. If a run crashes before releasing the lock, the transient sits there for up to an hour and no cron runs until it expires. You can clear it manually by running wp transient delete doing_cron with WP-CLI, or by deleting the row from wp_options where option_name = '_transient_doing_cron'.
How often should the real cron job fire?
Every 5 minutes is the sweet spot for most sites. Every 1 minute puts pointless load on shared hosting. Every 15 minutes is fine for a blog with only scheduled posts. If you use WooCommerce, Action Scheduler, or a plugin that queues many small jobs, stick with 5 minutes so the queue does not back up.
Do managed hosts like WP Engine or Kinsta need this?
No. They already replace wp-cron with a system cron that fires every minute or so. On those hosts you leave the default alone. If you set your own cron on top, you can double-fire events and confuse plugins like Action Scheduler. Check your host's documentation before touching anything.
Will disabling wp-cron break scheduled emails or WooCommerce?
Only if you forget to add the replacement server cron. WooCommerce order emails, Action Scheduler queues, MailPoet campaigns, WPForms notifications and every other scheduled task all rely on the same cron API. Once the server cron is hitting wp-cron.php on schedule, they all work again, usually more reliably than before.

Related Guides