
WordPress ships with a clever-but-fragile scheduler called WP-Cron. It handles everything from publishing scheduled posts to running plugin cleanup routines. The problem is that it is not a real scheduler. It piggybacks on visitor traffic: every time someone loads a page, WordPress checks whether any scheduled tasks are overdue and runs them inline with that request. On a quiet site, jobs get missed entirely. On a busy site, dozens of requests can trigger the same job simultaneously, causing load spikes that hurt your response times and confuse your cache.
Replacing WP-Cron with a genuine server-side cron job solves both problems at once. Jobs run on a predictable clock, not on visitor luck, and they run exactly once per interval instead of piling up. The switch takes less than five minutes and involves editing one line in wp-config.php plus one line in your server’s crontab.
What WP-Cron Actually Does
When WordPress bootstraps on each page load, it calls wp_cron(). That function compares the current time against a list of scheduled events stored in the wp_options table (under the cron key). If any event is overdue, WordPress runs it immediately, still inside the same PHP process that is serving the visitor’s page.
This design has two structural weaknesses:
- Missed jobs on low-traffic sites. If nobody visits your site at 3 AM, your nightly backup job never fires. It will eventually run when the next visitor arrives, but that might be hours late.
- Job pile-ups on high-traffic sites. Under heavy traffic, many concurrent requests can all see the same overdue job at the same millisecond. Without a proper locking mechanism, the job can run dozens of times in parallel, wasting CPU, bloating your database, and potentially sending duplicate emails or notifications.
A real cron job sidesteps both problems because the operating system scheduler invokes it on a fixed clock, independently of web traffic.
Step 1: Disable WP-Cron’s Built-In Trigger
Open your site’s wp-config.php file and add the following constant before the line that reads /* That's all, stop editing! */:
define( 'DISABLE_WP_CRON', true );Setting this to true tells WordPress to stop spawning cron from page loads. Scheduled events remain in the database, and WordPress will still run them when called correctly. You are simply cutting the traffic-triggered wire and handing control to the system scheduler instead.
Do not skip this step. If you add a system cron without disabling the built-in trigger, you will end up running jobs twice: once from the crontab and once from every page load that happens to arrive just after an event falls due.
Step 2: Add a System Cron Job
The cleanest way to invoke the WordPress scheduler externally is through WP-CLI, the standard command-line tool for WordPress administration. WP-CLI’s cron event run command triggers the scheduler exactly the way WordPress expects, without the overhead of a full HTTP request and without depending on your web server being idle.
Open your crontab for editing:
crontab -eAdd this line, adjusting the path to match your installation:
*/5 * * * * /usr/local/bin/wp cron event run --due-now --path=/var/www/your-site --quietA few notes on what each part does:
*/5 * * * *runs the command every five minutes. WordPress’s own scheduler works in five-minute intervals by default, so this is the right cadence. You can tighten it to every minute if any of your plugins schedule very frequent tasks./usr/local/bin/wpis the full path to the WP-CLI binary. Cron runs in a stripped environment and may not findwpon your PATH, so always use the absolute path. Confirm yours withwhich wpin a logged-in shell.--due-nowtells WP-CLI to run only events that are currently due, which mirrors exactly what WP-Cron would have done.--path=/var/www/your-sitepoints WP-CLI at your WordPress root. Replace this with your actual document root.--quietsuppresses output so cron does not generate email noise for every successful run.
If WP-CLI is not available on your server, the fallback is to use curl or wget to hit the wp-cron.php endpoint directly:
*/5 * * * * curl --silent --max-time 30 https://yourdomain.com/wp-cron.php?doing_wp_cronThis works, but the WP-CLI approach is preferable. It bypasses your web server entirely, does not consume an HTTP connection slot, and works even if your site is behind a Cloudflare orange-cloud proxy that might cache or block the request.
Step 3: Verify Everything Is Working
After saving the crontab, wait a few minutes and then check that scheduled events are still being processed. WP-CLI makes this easy:
wp cron event list --path=/var/www/your-siteThis shows every scheduled event along with its next run time. If the timestamps look reasonable (nothing catastrophically overdue), the system cron is doing its job. You can also force an immediate run to confirm the plumbing works:
wp cron event run --due-now --path=/var/www/your-siteFor ongoing monitoring, look at what events are registered and how often they fire. Some plugins are surprisingly aggressive schedulers and register intervals of every minute or even every thirty seconds. Seeing those events in the list is a useful prompt to review whether those plugins are truly necessary.
Caveats and Edge Cases
Multisite installations
On a WordPress Multisite network, WP-Cron runs per site. If you have many subsites, running --due-now against the network root may not catch every subsite’s events. You may need a separate cron entry per subsite, or use the --url flag to target individual sites. Check the WP-CLI documentation for your specific multisite configuration.
User context
Cron jobs run as a system user. Make sure the user running the crontab has read access to your WordPress files and write access to any directories WordPress needs to write to (uploads, cache, and so on). On many shared or managed setups, crontab entries run as the site’s PHP user, which is already correct.
Plugins that use custom intervals
Some plugins register their own cron intervals shorter than five minutes. If you have a plugin that needs to fire every minute, change the crontab schedule from */5 to * * * * *. The overhead is negligible, and a once-per-minute WP-CLI call is far cheaper than the random pile-ups the old system could produce.
Cache interactions
If you use full-page caching (LiteSpeed Cache, for example) or persistent object caching via Redis, cron jobs that modify posts, options, or transients should trigger cache purges automatically through standard WordPress hooks. If you notice stale data after cron runs, check that your cache plugin is hooking into the relevant WordPress actions and purging appropriately.
A Note on Managed Hosting
Some managed WordPress hosts configure real cron for you automatically or provide a control panel toggle for it. If your host does this, verify which constant they set and whether their cron interval matches your needs before adding your own crontab entry. Running both can cause the same duplication problem you were trying to avoid.
Takeaway
WP-Cron was a reasonable idea for a simpler era of WordPress hosting. Today it causes real problems: missed jobs, doubled jobs, and surprise latency spikes that can push your Core Web Vitals in the wrong direction. Setting DISABLE_WP_CRON to true and adding a five-minute system crontab entry is one of the highest-value-for-effort changes you can make to a production WordPress site. It takes five minutes to set up, requires no plugins, and makes your scheduled tasks genuinely reliable for the life of the site.