Skip to content

Why scheduled posts don’t publish on time — inspecting WP-Cron with WP-CLI

A post scheduled to publish at a specific time doesn’t go live when expected. A plugin’s recurring email notification never arrives. This tends to happen on low-traffic sites, and there’s a specific reason for it.

Note: WP-Cron is WordPress’s built-in scheduling system. It sounds like the OS-level cron daemon, but the underlying mechanism is quite different.

WordPress’s WP-Cron doesn’t work like a real OS cron daemon. On every page load, WordPress checks whether any scheduled task is past its due time and, if so, runs it. This is what’s known as “pseudo-cron” — and its weakness is that nothing runs without a page visit. Schedule a post to publish at 3am on a site with little overnight traffic, and the publish task can sit unexecuted until the next visitor happens to load a page.

WP-CLI lets you look inside this otherwise invisible system and run exactly the task you need, right now.

Listing what’s scheduled

wp cron event list
hook next_run_gmt recurrence
publish_future_post 2026-06-20 03:00:00
wp_version_check 2026-06-20 06:12:00 12 hours
wp_scheduled_delete 2026-06-21 00:00:00 daily

hook is the task’s identifier, next_run_gmt is the next scheduled run time in UTC, and recurrence is the repeat interval. If publish_future_post is still listed despite its time having already passed, that confirms the task is overdue simply because no page load has triggered it yet.

Running a task right now

To trigger a specific task immediately:

# Run a specific hook right now
wp cron event run publish_future_post

# Run every overdue task at once
wp cron event run --due-now

--due-now finds every task whose scheduled time has passed but hasn’t run yet, and executes all of them. Instead of waiting for a visitor to trigger the check, this one command runs the post publish, the email notification, or whatever else is pending.

Confirming WP-Cron itself is working

wp cron test

This checks whether the WP-Cron scheduler is functioning at all. On sites where wp-config.php has define('DISABLE_WP_CRON', true); set, the pseudo-cron mechanism is disabled entirely, and something else — a manual call or an external scheduler — has to call wp cron event run --due-now on a regular basis.

Checking available recurrence intervals

wp cron schedule list
name display interval (seconds)
hourly Once Hourly 3600
twicedaily Twice Daily 43200
daily Once Daily 86400

Plugins that register custom intervals (e.g. every 15 minutes) show up here too. This is also where an unexpectedly frequent task can be spotted.

Switching to a real cron

Setting DISABLE_WP_CRON to true and calling WP-CLI from the server’s actual crontab instead removes the dependency on page visits entirely — tasks then run at a guaranteed time.

# crontab example — every 5 minutes
*/5 * * * * cd /path/to/wordpress && wp cron event run --due-now > /dev/null 2>&1

The more common alternative — hitting wp-cron.php over HTTP with curl on a schedule — works too, but calling WP-CLI directly skips the HTTP overhead and avoids exposing wp-cron.php as a publicly reachable endpoint that has to be hit from outside the server.

Verifying after running

# Confirm the scheduled post is now published
wp post list --post_status=publish --orderby=date --order=DESC --posts_per_page=3

# Confirm no overdue tasks remain
wp cron event list

If publish_future_post no longer appears in the list, the corresponding post has already gone through its publish step.

Summary

What you need Command
List scheduled tasks wp cron event list
Run a specific task now wp cron event run <hook>
Run every overdue task wp cron event run --due-now
Confirm WP-Cron is working wp cron test
List available recurrence intervals wp cron schedule list

Knowing that WP-Cron is a page-visit-dependent pseudo-cron is what makes wp cron event list the first move when a scheduled publish doesn’t go through. Like recovering from a wp-admin lockout or serialization-safe replacement, this is WP-CLI reaching past the admin dashboard to the mechanism actually running underneath it.