The name "WP-Cron" is misleading - it is not Linux cron. It has no guaranteed schedule, no background thread and no real reliability. It depends on real visitor HTTP requests to trigger its check. On a low-traffic site that breaks the entire scheduling system.
Why this matters
When a visitor requests a page, WordPress generates the response and sends it. After sending, it checks whether any task with a next_run in the past should fire and runs it. That is cron-like, not cron. Consequences:
- Low-traffic sites (less than one request per 15 minutes) - tasks scheduled for 03:00 only run when the first visitor arrives at 08:00, if at all. A 04:00 backup behaves the same way.
- Heavily cached sites - pages are served from cache without hitting PHP, so WP-Cron is not triggered. We have seen sites where 95% of requests are served by Varnish/Cloudflare and WP-Cron almost never fires.
- Per-request overhead - WP-Cron does an internal
wp_remote_post()loopback towp-cron.phpon every request, adding 100-300ms of initial latency. - Concurrent runs under load - two visitors arriving at the same time can run the same task twice.
The healthy production setup is to disable internal WP-Cron and rely on a real system cron that fires on a fixed schedule.
How to detect
Tools > Site Health flags WP-Cron failing to run. Via WP-CLI:
wp cron event list --fields=hook,next_run_relativeHooks with significantly negative next_run_relative indicate misses. RankPlus checks DISABLE_WP_CRON in wp-config.php and the last-run timestamps of critical hooks.
How to fix
- Step 1 - disable internal WP-Cron: edit
wp-config.phpand before the/* That's all, stop editing! */line add:define( 'DISABLE_WP_CRON', true ); - Step 2 - set up system cron. cPanel: Cron Jobs > Add new cron job. Plesk: Tools & Settings > Scheduled Tasks. VPS via SSH:
crontab -e. - Add the entry:
Replace*/5 * * * * wget -q -O - https://example.com/wp-cron.php?doing_wp_cron > /dev/null 2>&1example.comwith your domain.*/5means "every 5 minutes". - For very active sites (WooCommerce taking live orders, scheduling systems) use
*/1(every minute). - If the host does not support cron, web-cron services like cron-job.org or EasyCron hit a URL on the cadence you choose. Free for typical use.
- A more accurate WP-CLI alternative (no HTTP roundtrip):
*/5 * * * * cd /var/www/html && wp cron event run --due-now --allow-root. - Verify it works: install WP Crontrol > Tools > Cron Events. The last-run column should refresh every 5 minutes.
Common mistakes
- Disabling WP-Cron without configuring a replacement: nothing schedules-driven runs ever again. Backups, emails, order statuses all freeze.
- Cron every minute on shared hosting: some hosts limit cron frequency.
*/5or*/10is safer. - Plain curl without flags:
curl https://...without-s -o /dev/nullmails the output to root and floods the mailbox. - Not verifying after the change: it does not always work first try. Confirm WP Crontrol shows fresh runs within 10 minutes.
- Pointing cron at HTTP when HTTPS is enforced: cron hits a 301 redirect instead of executing. Always use the full HTTPS URL.
Verifying the fix
Wait 10 minutes after configuring cron. In WP Crontrol > Cron Events, active hooks should show next_run within the next 5 minutes. Schedule a test post for 5 minutes ahead - it should publish on time. RankPlus turns green. If there is an issue, check the server cron log (/var/log/syslog or cPanel > Cron Email).
/wp-cron.php - otherwise the cron request can hit a cache and never reach PHP.