WordPress’s built-in task scheduler, WP-Cron, handles publishing scheduled posts, sending automated emails, running plugin routines, triggering backups. Most site owners never think about it until something stops working. The core problem: WP-Cron doesn’t run on a real, independent schedule — it fires when someone visits the site. Fine on a busy site; on a low-traffic one, scheduled tasks can be delayed or skipped entirely, a post scheduled for 9am not actually publishing until the next visitor shows up at 11am.
It’s Not Only About Traffic Volume
Worth understanding the actual mechanism, not just “low traffic = delays”: each time WP-Cron fires, WordPress sets a lock (the doing_cron transient) meant to stop a second overlapping run from starting while one is already in progress — but that lock has a fixed default timeout of 60 seconds (the WP_CRON_LOCK_TIMEOUT constant), regardless of whether the task itself is actually finished by then. A heavy scheduled task — a large backup, a multi-megabyte feed import, a site-wide regeneration routine — that runs longer than 60 seconds lets its own lock expire while it’s still executing, meaning the next visitor’s page load can spawn a second, overlapping run of the very same task. That’s the opposite problem from simple lateness: not a task failing to fire, but two copies of it firing at once and potentially colliding — duplicate emails, two backup processes fighting over the same files. This is exactly why server-level cron, running WordPress’s scheduler on its own fixed interval regardless of visitor activity or what else happens to be running, is the more reliable fix — it doesn’t remove the underlying lock mechanism, but it removes the unpredictable visitor-triggered timing that makes hitting this edge case more likely in the first place.
Seeing What’s Actually Scheduled
WP Crontrol (free, actively maintained, 300,000+ installs) gives a full list of scheduled events, their intervals, and next-due times before making any changes — install it, then check Tools → Cron Events. If a plugin’s scheduled task is missing from that list entirely, that’s the actual reason it isn’t running, more useful diagnostic information than guessing.
Replacing WP-Cron With a Real Server Cron Job
In wp-config.php, add this line directly above /* That's all, stop editing! Happy publishing. */:
define('DISABLE_WP_CRON', true);
This stops WordPress triggering its scheduler on page load entirely — tasks won’t fire again until the server-side replacement is in place, so both steps genuinely need doing together, not one without the other. In the hosting control panel (cPanel or similar), find Cron Jobs under Advanced, set the frequency to every minute or every five minutes, and run:
wget -q -O - https://yourdomain.com/wp-cron.php?doing_wp_cron > /dev/null 2>&1
Or with curl, if that’s what the host supports:
curl -s https://yourdomain.com/wp-cron.php?doing_wp_cron > /dev/null 2>&1
Replace yourdomain.com with the real domain — if the site sits in a subdirectory, that needs including in the path too. The > /dev/null 2>&1 suppresses output so an email doesn’t arrive every single time the cron fires. Once saved, the server calls WordPress’s scheduler directly at the set interval, independent of site traffic entirely — a single predictable trigger source instead of however many overlapping visitor-triggered ones might otherwise collide, meaningfully reducing (though not eliminating) the overlapping-run risk described above.
Back in WP Crontrol’s Tools → Cron Events, confirm the Next Run timestamps update as expected against the interval just configured — add a temporary test event to confirm it fires correctly if there’s any doubt. Every minute is a reasonable default for most sites — keeps scheduled posts, email triggers, and backup routines close to on-time without meaningful server load. Hosts without cron jobs directly in the control panel often still offer SSH access for editing the crontab manually via crontab -e — worth checking documentation before assuming it’s unavailable. Managed hosts like Kinsta or WP Engine frequently handle this replacement automatically at the server level already — check their documentation before touching wp-config.php at all, since the change may not be needed. Running scheduled backups alongside this is worth double-checking too, to confirm the backup plugin is correctly configured against whatever cron setup ends up in place.
Adding DISABLE_WP_CRON without setting up the replacement means no scheduled tasks run at all — both steps genuinely need doing together. A wrong path in the cron command (missing subdirectory, wrong domain) means the URL never resolves correctly. An interval set too infrequently — once an hour — means scheduled posts could run up to 59 minutes late; every five minutes is a reasonable balance for most sites. Nothing to configure at all on a fully managed host that already handles this; the steps above cover most shared-hosting/cPanel situations; a VPS with command-line comfort gets more control editing the crontab directly than through a cPanel interface. Default WP-Cron is usually fine for scheduled post publishing alone — the server cron setup earns its keep most clearly for email marketing integrations, backup plugins, or anything where precise timing genuinely affects the experience. The official WordPress developer documentation on WP-Cron covers the full scheduling API for anything needing to go deeper than this.

Etienne Basson works with website systems, SEO-driven site architecture, and technical implementation. He writes practical guides on building, structuring, and optimizing websites for long-term growth.