WP-Cron is how WordPress runs scheduled work: publishing a post at 09:00, sending a newsletter, taking a backup. It works fine on a busy site. On a quiet one, or behind a cache, tasks run late or not at all, and WordPress does not tell you. The fix is old and simple: let a real scheduler start those tasks instead.
In short
- 1Have a real scheduler call
https://yoursite.com/wp-cron.phpevery 5 minutes. - 2Add
define( 'DISABLE_WP_CRON', true );to wp-config.php. - 3Check that tasks run on time and nothing is overdue.
What WP-Cron actually does
WordPress keeps a list of scheduled tasks in the database, each with the time it is due. On every page load,
WordPress looks at that list. If something is due, it sends a request to wp-cron.php on its own
site in the background, and that request runs the tasks.
So WP-Cron is not a clock. It is a check that rides along on visitor traffic. That works on a busy site and fails in a few predictable ways:
- No visitors, no tasks. A post due at 09:00 on a quiet site goes out when the first visitor arrives, which might be noon.
- Caching hides the visits. With full page caching or a CDN, most visits are answered without PHP and never trigger the check.
- The background request can fail. That request to itself (a loopback) is blocked by some hosts, firewalls, password protection or broken SSL, and then nothing runs at all.
- It costs your visitors. The check and the extra request happen during real page loads.
Signs WP-Cron is letting you down
- Posts show Missed schedule instead of going live.
- WooCommerce piles up pending scheduled actions under WooCommerce › Status › Scheduled Actions.
- Newsletters, reminders and follow-up emails go out late or in bursts.
- Backup plugins report that the last backup is older than its schedule.
- Tools › Site Health says a scheduled event is late, or that your site could not complete a loopback request.
To see the schedule itself, install the free WP Crontrol plugin and open Tools › Cron Events. Anything with a next run in the past is overdue. With WP-CLI you get the same list on the command line:
# Every scheduled task and when it is due wp cron event list # Can WordPress run WP-Cron at all? wp cron test
Step 1: Set up a real trigger
Start here, before touching wp-config.php. If the new trigger works first, switching the old one off leaves no gap. There are three ways to do it. Pick the first one that fits your hosting.
Option A: A crontab on your own server
For a VPS or dedicated server where you have SSH. Open the crontab of the user that owns the WordPress files
with crontab -e and add one of these lines. WP-CLI is the better choice when it is installed,
because it runs the tasks directly without going through the web server.
# With WP-CLI: run every task that is due, every 5 minutes */5 * * * * cd /var/www/yoursite && wp cron event run --due-now --quiet # Without WP-CLI: call wp-cron.php over HTTP */5 * * * * curl -fsS --max-time 60 "https://yoursite.com/wp-cron.php?doing_wp_cron" > /dev/null 2>&1
Replace /var/www/yoursite and yoursite.com with your own path and address.
The catch with a crontab is that it fails silently: if the server is down or the command breaks, nobody tells you.
Option B: Your hosting control panel
Most shared hosting has a scheduler in the control panel: Cron Jobs in cPanel, Scheduled Tasks in Plesk, Cron Jobs in DirectAdmin. Choose an interval of 5 minutes and use this command:
wget -q -O - "https://yoursite.com/wp-cron.php?doing_wp_cron" > /dev/null 2>&1
Some hosts only allow an interval of 15 minutes or more on cheaper plans, and like a crontab, a failed run is not reported anywhere.
Option C: An external cron service
The easiest choice when you have no server access, use managed hosting, or look after several sites. An outside
service calls wp-cron.php over the internet on a fixed schedule, so it works on any host. A good one
also keeps a history of every call and tells you when one fails, which a crontab never does.
With CronEngine, create a job with this address and a schedule of every 5 minutes, then press Test in the form. WordPress should answer 200.
https://yoursite.com/wp-cron.php?doing_wp_cron
The free plan runs jobs as often as every 15 minutes, which is fine for a blog. Every 5 minutes starts on the Starter plan at €5.99 a month.
Step 2: Switch off the built-in WP-Cron
Once the new trigger has run a few times, tell WordPress to stop starting tasks on page loads. Open
wp-config.php in the root of your site and add this line above
/* That's all, stop editing! */:
// Scheduled tasks are now started by a real cron job.
define( 'DISABLE_WP_CRON', true );
This does not delete or pause anything. Every task stays scheduled, and wp-cron.php still runs
them whenever it is called. Only the check on page loads is gone.
Step 3: Check that it works
- In WP Crontrol, no event should have a next run more than one interval in the past.
- Schedule a test post a few minutes ahead and see that it goes live on time without visiting the site.
- On WooCommerce, the number of pending scheduled actions should go down, not up.
- If you use a cron service, its history should show a row of 200 answers.
A 401 or 403 answer usually means a firewall, security plugin or password protection blocks the call. A 404 means the address is wrong. A 500 means one of the tasks ran into an error, and the reply often says which plugin.
How often should it run?
Calling wp-cron.php when nothing is due costs almost nothing: it checks the list and stops. So pick the
interval by how late a task may be, not by server load.
| Site | Interval |
|---|---|
| Blog or brochure site | Every 15 minutes |
| Most sites, scheduled posts, newsletters | Every 5 minutes |
| WooCommerce, memberships, bookings | Every 1 to 5 minutes |
Special cases
Multisite
Each site in a network has its own schedule, so each needs its own call: https://site-one.example/wp-cron.php,
https://site-two.example/wp-cron.php and so on. With WP-CLI, one crontab line can run them all:
*/5 * * * * cd /var/www/network && wp site list --field=url | xargs -I % wp cron event run --due-now --quiet --url=%
Firewalls, security plugins and Cloudflare
If the call is blocked, allow the address it comes from in your firewall or security plugin. For CronEngine
that is 45.9.188.216. With Cloudflare, also make sure no bot or challenge rule applies to
/wp-cron.php.
Staging sites behind a password
A site behind HTTP Basic authentication needs the username and password in the call. A crontab can use
curl -u user:password, and most cron services, CronEngine included, have a field for it.
Managed WordPress hosting
Some managed hosts already run WP-Cron from their own server and may even set DISABLE_WP_CRON for you.
Check their documentation before changing anything. If they do, you can still add an external call for the run history and alerts.
When nothing outside can reach the site
For a site on an internal network with a broken loopback, WordPress has a fallback: define( 'ALTERNATE_WP_CRON', true ); in wp-config.php runs tasks through a redirect on page loads instead. It still depends on visitors and adds a
parameter to URLs, so treat it as a last resort.
Questions
Is it safe to disable WP-Cron?
Yes, as long as something else calls wp-cron.php. Disabling it only stops page loads from starting tasks. Every scheduled task stays where it is and runs the next time wp-cron.php is called. Set up the new trigger first and the switch has no gap.
Do I need the ?doing_wp_cron part of the URL?
No. WordPress adds it to its own calls, and wp-cron.php works the same without it. Keeping it does no harm, and some caching setups skip URLs with a query string, which helps the call reach PHP.
What if a task takes longer than the request timeout?
wp-cron.php tells PHP to keep going when the caller disconnects, so a slow task normally still finishes. Your scheduler will report a timeout for that run, which is a useful hint that one of your plugins has become slow.
My site gets plenty of traffic. Do I still need this?
Usually, yes. With full page caching or a CDN in front of the site, most visits never reach PHP, so they never trigger WP-Cron at all. And a busy site still pays for the check on visitor requests. A real schedule is more predictable either way.
Will this make my site faster?
A little. Page loads no longer check the schedule or start the background request, and tasks no longer run in the same moments as your visitors. The difference is biggest on cheap shared hosting.
Can I go back to the old behaviour?
Yes. Remove the DISABLE_WP_CRON line from wp-config.php and stop the external schedule. Nothing else changed, so WordPress carries on exactly as before.
Spotted a mistake, or something that behaves differently for you? Tell us and we will update the guide.