WatchCron

How to Replace WP-Cron with an External Cron Job

WordPress ships with a built-in task scheduler called WP-Cron. It handles scheduled posts, plugin updates, email digests, cache clearing, and dozens of other background jobs. The problem: it only fires when someone visits your site.

On a busy WooCommerce store with thousands of daily visitors, that's usually fine. On a company blog that gets fifty hits a day, your "daily backup at 3 AM" might not run until someone stumbles onto the homepage at noon. Scheduled posts go live hours late. Transient cleanup never happens. WooCommerce order exports pile up.

Replacing WP-Cron with a real, time-based trigger fixes all of this. Below we cover the three main approaches: system crontab, hosting panel cron, and external cron services. Pick the one that matches your setup.

Why WP-Cron misses jobs

WP-Cron is not a real cron daemon. WordPress checks a timestamp in the database on every page load, and if any scheduled events are overdue, it spawns a background HTTP request to wp-cron.php to process them. No page load, no check.

Several things make this worse:

Full-page caching. If your caching plugin or CDN serves a static HTML response, WordPress never executes PHP on that request, and the cron check never runs. Visitors keep seeing cached pages while background jobs sit idle.

Low traffic. A staging site, an intranet portal, or a new blog with ten visitors a day will have long gaps between cron runs. Backups that were supposed to run at midnight wait until the first morning visitor at 8 AM.

Race conditions. When two visitors hit the site at the same instant, WordPress can spawn two parallel cron processes. Both read the same overdue events, both try to execute them. Depending on the plugin, you end up with duplicate emails, double order processing, or database locks.

Security plugins and firewalls. Plugins like Wordfence or Sucuri sometimes block the loopback request that WordPress makes to its own wp-cron.php. The cron trigger fires, but the HTTP request gets a 403 or times out. No error appears in the admin dashboard.

Step 1: disable the built-in trigger

Before setting up an external trigger, tell WordPress to stop checking for overdue events on every page load. Open wp-config.php and add this line above the "That's all, stop editing!" comment:

define('DISABLE_WP_CRON', true);

This does not delete scheduled events. Plugins can still register tasks, and those tasks stay in the queue. You're only removing the "check on every page load" behavior. From now on, wp-cron.php needs an explicit HTTP request or WP-CLI call to process the queue.

One quick note: if you're not sure external cron will work on your setup, there's a middle ground. WordPress has a lesser-known constant called ALTERNATE_WP_CRON:

define('ALTERNATE_WP_CRON', true);

This redirects the visitor's browser to wp-cron.php via a client-side redirect instead of a server-side loopback. It bypasses firewall and loopback issues, but still depends on traffic. Worth trying if you're on restrictive shared hosting where nothing else works.

Method 1: system crontab (full server access)

If you have SSH access to your server, this is the most reliable method. You're using the operating system's cron daemon, which runs on a clock. No visitors needed.

Option A: trigger via HTTP

The classic approach. Add this to your crontab with crontab -e:

*/5 * * * * wget -q -O /dev/null https://yoursite.com/wp-cron.php >/dev/null 2>&1

This hits wp-cron.php every 5 minutes via HTTP. The -q flag silences wget, and -O /dev/null discards the response body. If you prefer curl:

*/5 * * * * curl -s https://yoursite.com/wp-cron.php >/dev/null 2>&1

Both work. One catch: this request comes from your own server and hits the public URL, which means it goes through your web server, any reverse proxy, and any security rules you have in place. If you've restricted wp-cron.php by IP or added basic authentication to your staging site, this request will fail silently.

Option B: trigger via WP-CLI

Most articles skip this, but WP-CLI is actually the better approach. Instead of making an HTTP request, you invoke WordPress directly from the command line:

*/5 * * * * cd /var/www/yoursite && wp cron event run --due-now --quiet 2>&1 | logger -t wp-cron

Why this beats the HTTP method:

No HTTP overhead. The command runs PHP directly, bypassing Apache/Nginx entirely. No firewall issues, no SSL certificate problems, no self-signed cert warnings on staging.

No loopback dependency. Security plugins can't block it because there's no HTTP request to block.

Better error visibility. Piping to logger sends output to syslog. If a cron event throws a PHP fatal, you'll see it in /var/log/syslog instead of it vanishing into a discarded HTTP response.

One caveat: WP-CLI must be installed, and the cron entry needs to run as the correct user (usually www-data or the site owner). On most modern hosting setups with PHP-FPM, this works out of the box.

Method 2: hosting panel cron (cPanel, Plesk, DirectAdmin)

If you don't have SSH but your hosting provides a cron job manager in the control panel, you can set up the same HTTP trigger through the UI. The process varies by panel, but the core setup is identical.

In cPanel, go to "Cron Jobs" under the Advanced section. Set the interval to every 5 minutes (or use the common setting dropdown), and enter this as the command:

wget -q -O /dev/null https://yoursite.com/wp-cron.php >/dev/null 2>&1

Plesk users: go to Scheduled Tasks in your domain settings. DirectAdmin: it's under "Cron Jobs" in the advanced features menu.

The downside here is that you're still running the job from your own server. If your hosting has reliability issues (the server restarts, the cron daemon gets killed during a migration, the hosting provider throttles background processes), your WordPress cron stops too. You also can't monitor whether the job actually ran and completed successfully. It either works or silently doesn't.

Method 3: external cron service

An external service hits your wp-cron.php URL from outside your server, on a schedule you define. The trigger comes from an independent system, which means your WordPress cron runs even if your server has internal issues with loopback requests or process limits.

A quick comparison:

ServiceFree tierMin intervalTimeoutAlertsNotes
cron-job.orgUnlimited jobs1 min30 secEmail onlyFree, community-run. No SLA, 30-second timeout can be tight for heavy tasks
EasyCron1 job20 min5 secEmailFree tier very restricted. Paid from $12/year
FastCron15 jobs5 minVariesEmail, SlackHas a free WordPress plugin on wordpress.org
UptimeRobot50 monitors5 minN/AEmail, SMSUptime monitor, not a cron tool. Can trigger wp-cron.php as a side effect but won't catch failures
WatchCron3 cloud cron tasks1 minConfigurableEmail, Slack, Telegram, Discord, webhooksRuns the job AND monitors result. Logs HTTP status, response time, body for every execution. Alerts on failures

For most WordPress sites, a 5-minute interval is plenty. Scheduled posts publish within 5 minutes of their scheduled time, backups fire close to the target hour, and email queues drain regularly.

The setup is straightforward with any of these: paste your https://yoursite.com/wp-cron.php URL, pick a 5-minute or 15-minute schedule, save. The service will send a GET request at each interval.

What most of these services don't tell you: they trigger the job, but they don't verify it completed. If wp-cron.php returns a 200 status but the actual event inside threw a fatal error, you won't know. The HTTP response from wp-cron.php is always empty. WordPress doesn't report success or failure in the response body.

Monitoring the trigger (not just running it)

This is the gap none of the usual tutorials cover. You've set up an external cron, you've disabled the built-in trigger, and everything seems fine for weeks. Then a WordPress update changes something, or a security plugin adds a new rule, and the cron silently breaks.

With a system crontab or hosting panel cron, you can add basic alerting by checking the HTTP response code. But that only catches complete failures (500 errors, timeouts). It won't catch partial failures, gradual slowdowns, or the job returning 200 while doing nothing useful inside.

Cloud cron services that combine execution with monitoring solve this properly. WatchCron's Cloud Cron for WordPress runs the scheduled request and tracks the HTTP status, response time, and response body for every run. If the response code changes, the response time spikes, or the request times out, you get an alert through Slack, Telegram, email, or other channels. Not a silent entry in a log file you'll never check.

The free plan includes 3 cloud cron tasks, which covers a typical WordPress site (one wp-cron.php trigger every 5 minutes). If you run multiple WordPress sites (staging and production, or a multisite network), the Starter plan at $7/month gives you 15 cloud cron tasks with Slack and Telegram alerts.

Troubleshooting common issues after the switch

Security plugins blocking wp-cron.php

Wordfence, Sucuri, and iThemes Security can block external requests to wp-cron.php. If your external cron gets 403 responses, check your security plugin's firewall rules. You may need to whitelist the IP addresses of your cron service. Most services publish their IP ranges. cron-job.org, EasyCron, and FastCron all document theirs.

Basic auth on staging sites

If your staging environment uses HTTP basic authentication (the browser username/password popup), external cron requests will get a 401 response. Either exclude wp-cron.php from basic auth in your server config, or pass credentials in the URL:

https://user:[email protected]/wp-cron.php

Not elegant, but it works. Some services let you set custom headers with base64-encoded credentials instead.

WordPress multisite needs per-site triggers

On a WordPress Multisite network, each subsite has its own cron queue. A single request to the main site's wp-cron.php only processes events for that site. For a network with 5 subsites, you need 5 separate cron triggers (one for each site's URL). For larger networks, look into the wp cron event run --due-now --url=SITE_URL WP-CLI approach, which lets you loop through all sites in a single script.

The job runs but does nothing

Sometimes wp-cron.php returns successfully but no events actually execute. Check two things:

First, verify events are actually scheduled. Install the WP Crontrol plugin and look at the event list in Tools > Cron Events. If the list is empty, your plugins aren't registering events. That's a plugin issue, not a cron issue.

Second, check the cron lock. WordPress stores a _transient_doing_cron value in the options table. If a previous cron run crashed mid-execution, this lock can get stuck. Delete it manually from the database or use WP-CLI:

wp transient delete doing_cron

After clearing the lock, trigger wp-cron.php again and check WP Crontrol to confirm events are processing.

Which method to pick

For a single WordPress site where you have SSH access: WP-CLI via system crontab. Reliable, no external dependencies, good error visibility.

For managed hosting without SSH (WP Engine, Kinsta, Flywheel): check if your host already disables WP-Cron and runs a server-side cron. Most managed WordPress hosts do this by default. Kinsta runs it every 15 minutes; WP Engine uses their own system cron. You might not need to change anything.

For shared hosting or any setup where you want independence from your host: an external cron service. The trigger runs regardless of what's happening on your server, and if you pick one with monitoring, you'll actually know when things break.

For multiple sites, agencies managing client WordPress installations, or anyone who's been burned by silent cron failures: a service that runs the job AND tells you the result. That's the one thing a crontab entry can never do on its own.

The actual setup takes about five minutes. Most of that is finding wp-config.php in your file manager. The hard part was understanding which approach fits.

If you'd rather not wire this up yourself, WatchCron's Cloud Cron for WordPress handles both sides: it triggers wp-cron.php on your schedule and monitors every run automatically. You still need to add the DISABLE_WP_CRON constant, but everything else happens in the dashboard. The free plan covers a single WordPress site. Create a free account and set up your first cloud cron task in under two minutes.

For more on how cron expressions work, we have a complete guide. And the cron expression builder lets you construct and validate expressions visually before committing them to your crontab or cron service.

Frequently asked

No. Disabling WP-Cron only removes the "check on every page load" trigger. Plugins can still register scheduled events, and those events stay in the queue. You just need an external trigger (system cron, hosting panel cron, or external service) to process the queue on a regular schedule.

Every 5 minutes is the standard recommendation. Scheduled posts will publish within 5 minutes of their scheduled time, backups fire close to the target hour, and email queues drain regularly. For sites with time-sensitive tasks (WooCommerce order processing, real-time notifications), every 1 minute is better.

Technically yes. UptimeRobot can ping your wp-cron.php URL every 5 minutes as part of uptime monitoring. But it is an uptime tool, not a cron tool. It checks whether the URL responds, not whether the cron events inside actually executed. If wp-cron.php returns 200 but a scheduled task throws a fatal error internally, UptimeRobot will report "up" while your cron is broken.

Your scheduled events pile up in the queue and never run. Scheduled posts stay in draft, backups stop, email digests don't send, and plugin maintenance tasks (like clearing expired transients) never execute. Nothing will break immediately, but over days and weeks you'll notice missed posts, growing database size, and plugins behaving unexpectedly.