Troubleshooting guide · wordpress · Published 2026-08-16 · 4 min read
WordPress wp-cron troubleshooting
WordPress wp-cron troubleshooting: how scheduled jobs fire, the causes of stuck cron, and how to switch to a real scheduler.
- ·How WordPress schedules
- ·Find the failure
- ·Move to real cron
How WordPress scheduling actually works
WordPress does not run a background daemon like a server cron. Instead, on most sites it relies on wp-cron.php, which fires when a visitor loads a page: the request is checked for due events, and if any are due the site makes an internal request to wp-cron.php to run them. That is the behaviour-based cron: no visits means no cron, and a very quiet site can miss scheduled posts, scheduled image compression, plugin license checks, and the automatic update checks.
The failure mode is rarely "the site does not understand the schedule". It is almost always one of:
- No traffic triggers nothing at all.
DISABLE_WP_CRONis set totrue, which turns the visitor-triggered check off entirely, and no external cron feed was configured to replace it.wp-cron.phpis blocked: an IP-denial rule, a 404 rewrite battle, the CDN caching it (Cloudflare serving a cached empty document), or the server firewalling loopback requests.- A hung event with a stuck row in the cron option array blocks all later events.
The WordPress troubleshooting order is the right lens: figure out whether the scheduler, the event, or the request to wp-cron.php failed, not just that "the post did not publish".
Diagnose: the two checks
Check 1: is cron enabled but failing?
Read the last cron run via wp-cli if available:
wp cron event list --fields=hook,next_run_relative,status
A list with all events showing 0 next-run time, or a cluster of stuck "past-due but not run" entries, means the cron has not been triggered or is stuck.
Check 2: does direct hit to wp-cron.php work?
curl -I https://example.com/wp-cron.php
Expect a 200 with an empty body. A 404, 403, or a cached HTML page is the smoking gun. If the response is wrong, see the DNS and SSL and error pages pages for where the block lives; check the server error log path from the error log article.
The two common fixes
Enable the real cron and disable the visitor trigger.
If the host allows a crontab (most shared hosts give you Cron Jobs in cPanel), set the visitor check off and point a real scheduler at the check endpoint:
define( 'DISABLE_WP_CRON', true );
Then in cron (cPanel > Cron Jobs, or a host scheduled task), add a job that wakes WordPress at least every few minutes and processes due events. A reasonably reliable line:
curl https://example.com/wp-cron.php
or using PHP CLI when the CLI is available (hits the file directly without a web request):
php /path/to/wordpress/wp-cron.php
Expect delayed, not broken: WordPress cron is best-effort on passive timing. Jobs run on the next page load after they are due, and the wp-cron.php spawn is throttled to roughly once per minute on a busy site (the doing_cron transient). So a scheduled post set for 14:00 on a quiet site publishes on the next visit after 14:00, not at 14:00 exactly. If events consistently fire 10 minutes late on a site with normal traffic, look at the throttling and the stuck-row check above, not at a "bug" in the schedule itself.
When wp-cron is the wrong suspect
Some scheduler problems look like cron but are not:
- A scheduled post does not publish but
wp-cli cron event run --due-nowworks instantly: the issue is the trigger (blocked endpoint), not the time. - Email erratically delayed: mail and cron interact, but the check is in the error-handling-email-forms article; a stuck SMTP can delay the queue long past a cron event.
- The site just updated and events stopped: check for the equal-opportunity broken update 500, because a bad update can kill the loopback before the cron ever runs.
- Cached pages: a full-page cache can serve the index body without ever hitting
wp-cron.phpon the cached side, the classic silent cron killer; the caching quick wins covers the interplay.
Prevention
Run the actual wp-cron on a real timer from the start, keep DISABLE_WP_CRON false while you develop (or a real external schedule wins), and watch the "Scheduled (Cron) Events" row in Tools > Site Health > Info > Scheduled Actions for missed count after plugin changes. A site with a real cron has no "hidden visitor tax" and no stuck row surprises during updates.