WP-Cron Status: Why to Disable It and Move to System Cron

Internal WP-Cron does not run on schedule. Here is how to disable it, set up a server cron and finally get reliable timing.

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 to wp-cron.php on 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_relative

Hooks 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

  1. Step 1 - disable internal WP-Cron: edit wp-config.php and before the /* That's all, stop editing! */ line add:
    define( 'DISABLE_WP_CRON', true );
  2. Step 2 - set up system cron. cPanel: Cron Jobs > Add new cron job. Plesk: Tools & Settings > Scheduled Tasks. VPS via SSH: crontab -e.
  3. Add the entry:
    */5 * * * * wget -q -O - https://example.com/wp-cron.php?doing_wp_cron > /dev/null 2>&1
    Replace example.com with your domain. */5 means "every 5 minutes".
  4. For very active sites (WooCommerce taking live orders, scheduling systems) use */1 (every minute).
  5. 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.
  6. A more accurate WP-CLI alternative (no HTTP roundtrip): */5 * * * * cd /var/www/html && wp cron event run --due-now --allow-root.
  7. 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. */5 or */10 is safer.
  • Plain curl without flags: curl https://... without -s -o /dev/null mails 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).

Tip: If you use Cloudflare, add a Page Rule that bypasses cache for /wp-cron.php - otherwise the cron request can hit a cache and never reach PHP.