The Problem
You schedule a post for 9:00 AM. Nine o'clock comes and goes. The post is still sitting in "Scheduled" status โ no notification, no error, just silence. Refreshing the admin panel doesn't help. The post is stuck.
Almost every time this happens, the culprit is the same: wp-cron.
Root Cause
WordPress doesn't use a real system cron. Instead, it ships its own pseudo-scheduler called WP-Cron (wp-cron.php), which handles publishing, emails, plugin jobs, and other time-based tasks. The catch? It only fires when someone visits your site. No visitors, no cron runs.
That design works fine on busy sites. It falls apart in these situations:
DISABLE_WP_CRONis set totrueinwp-config.phpโ extremely common on managed hosts or after following a "performance optimization" guide- Low-traffic sites โ a blog getting 50 visitors a day might go hours between cron triggers
- Nginx configs or firewall rules blocking HTTP loopback requests (the server can't call itself)
- A plugin throwing a fatal PHP error that kills execution before cron ever runs
Step 1: Check if DISABLE_WP_CRON is Set
Open wp-config.php and search for this line:
define('DISABLE_WP_CRON', true);
Found it? That's your problem. Either remove the line entirely, or flip it to false:
define('DISABLE_WP_CRON', false);
Save the file. Create a test post scheduled 2โ3 minutes out and watch if it publishes on time.
Step 2: Test WP-Cron Manually
Before assuming the worst, trigger wp-cron directly and see what happens:
# Via WP-CLI (recommended)
wp cron event run --due-now
# Or hit this URL directly in your browser
https://yourdomain.com/wp-cron.php?doing_wp_cron
Errors from WP-CLI, or anything other than a 200 response from that URL, means something is blocking loopback requests.
Check Loopback Connectivity
# Run this from the server itself
curl -I https://yourdomain.com/wp-cron.php
A connection refused or timeout means the server can't make HTTP requests back to itself. This is common on certain VPS setups and behind reverse proxies with strict firewall rules.
Step 3: Replace WP-Cron with a Real System Cron
Visitor-triggered cron is unreliable by design. On any production site that depends on scheduled publishing, ditch it and use a real cron job instead. Set DISABLE_WP_CRON to true, then wire up the system cron yourself.
3a. Disable the Built-in WP-Cron
// In wp-config.php
define('DISABLE_WP_CRON', true);
3b. Add a System Cron Job
Edit your crontab:
crontab -e
Pick one of these โ running every 5 minutes is the sweet spot between responsiveness and server load:
# Option 1: Direct PHP (simplest)
*/5 * * * * php /var/www/html/wp-cron.php > /dev/null 2>&1
# Option 2: WP-CLI (more reliable, handles multisite)
*/5 * * * * cd /var/www/html && wp cron event run --due-now --quiet
# Option 3: wget/curl (useful if PHP path varies)
*/5 * * * * wget -q -O - https://yourdomain.com/wp-cron.php?doing_wp_cron >/dev/null 2>&1
On cPanel
Go to cPanel โ Cron Jobs, set the interval to every 5 minutes, and enter:
php /home/yourusername/public_html/wp-cron.php > /dev/null 2>&1
Step 4: Manually Publish Stuck Scheduled Posts
Posts already stuck in "Scheduled" limbo won't fix themselves once cron starts working again โ they need a nudge. WP-CLI handles this cleanly:
# See what's stuck
wp post list --post_status=future --fields=ID,post_title,post_date
# Publish a specific post (replace 123 with the actual ID)
wp post update 123 --post_status=publish
Need to rescue them in bulk?
wp post list --post_status=future --format=ids | xargs wp post update --post_status=publish
Check the dates first. This publishes everything in "future" status immediately โ including posts that were legitimately scheduled for next week.
Step 5: Check for Plugins Blocking Cron
Security plugins are frequent offenders. Wordfence and iThemes Security both have options to block direct access to wp-cron.php โ check their settings under firewall rules or "disable WP-Cron" toggles.
To isolate the issue, deactivate everything and test:
wp plugin deactivate --all
wp cron event run --due-now
If that works, reactivate plugins one at a time until the problem comes back. The last plugin you activated is your culprit.
Verify the Fix
- Create a new post and schedule it 2โ3 minutes in the future
- Wait for the time to pass
- Refresh the post list โ it should now show "Published"
You can also inspect the cron queue directly:
# List upcoming events
wp cron event list
# See when events are next due
wp cron event list --fields=hook,next_run_relative
Prevention
- Use a real system cron on any site where publishing timing matters โ WP-Cron is fine for development, not for production
- Install WP Crontrol while debugging. It shows every scheduled event in a readable table and lets you trigger them manually from the WordPress admin
- After migrating to a new server or host, verify cron is working before the site goes live. One test post scheduled 5 minutes out will tell you immediately
- High-volume scheduled tasks (WooCommerce emails, bulk exports) benefit from Action Scheduler, which adds queue management and retry logic on top of WP-Cron

