Bali, Indonesia
Insights

Why WordPress Shows Missed Schedule on Posts When the Site Gets Little Traffic

October 26, 2026
0:00
A post was scheduled for Monday morning, but on Monday afternoon it is still sitting in the dashboard with a red "Missed schedule" label. Nothing crashed, the site loads fine, and the hosting status page is green. This happens most often on small, quiet sites, and the usual first guess is that the server was down. It almost never was. The cause is how WordPress decides when to run scheduled work. This piece walks through the problem the way a post-mortem would: what the impact looks like, how to investigate it, the point where the cause becomes clear, how to recover quickly, and how to fix it so it does not come back.
The visible symptom is narrow. Scheduled posts stay in the future status after their publish time has passed, and the posts list shows "Missed schedule" next to them. Look a little wider and you usually find other quiet failures with the same root: scheduled backups that did not run, plugin cleanup jobs that never fired, WooCommerce or newsletter tasks that lag behind, and update checks that seem stale. A detail that confuses site owners is that the post often publishes itself the moment they log in to check on it. From their side it looks random. From the server side it is a strong clue, and we will come back to it. For support staff, this ticket tends to arrive as "your server missed my post". The useful first move is not to look at uptime graphs. It is to ask what was supposed to trigger the publish, and whether that trigger actually happened.
WordPress has its own task scheduler called WP-Cron. Despite the name, it is not the Linux cron daemon. It is a list of events stored in the database (in the cron option in wp_options), each with a timestamp and a hook name. When you schedule a post, WordPress adds a single event for the publish_future_post hook at the post's publish time. So the investigation has three questions. Is the event in the queue? Is WP-Cron allowed to run? And can WordPress actually start it? If you have shell access and WP-CLI, these read-only checks answer most of that in a minute:
BASH
# Is WP-Cron disabled in wp-config.php? (errors if the constant is not defined)
wp config get DISABLE_WP_CRON

# Which posts are still stuck as scheduled?
wp post list --post_status=future --fields=ID,post_title,post_date

# What is queued, and how overdue is it?
wp cron event list --fields=hook,next_run_relative

# Can WordPress spawn WP-Cron through a loopback request?
wp cron test
If the event list shows publish_future_post entries marked as overdue, the queue is fine. The events exist; nobody is running them. That rules out the common theory that a plugin deleted the schedule, and it points at the trigger. The second source of evidence is the access log for the domain. Every time WP-Cron is spawned, WordPress makes a POST request to /wp-cron.php on its own site. Searching the domain's access log for wp-cron.php tells you how often that has actually happened. Where those logs live depends on the control panel and web server, so check your panel's documentation for the per-domain log path. If you find long gaps with no wp-cron.php requests at all, you have found the window in which the post was missed.
Here is the part that explains everything. WP-Cron has no background process. On every normal page load, WordPress runs a function called wp_cron() early in the request. It looks at the queue, and if something is due, it fires a non-blocking request to wp-cron.php to run those events. A transient called doing_cron acts as a lock so that many visitors at once do not start many runs; how long that lock lasts is controlled by WP_CRON_LOCK_TIMEOUT. In other words, WordPress only checks the clock when a PHP request reaches it. If nobody visits, nothing runs. That is the whole reason quiet sites are affected and busy sites rarely are. A post scheduled for 8:00 on a site with no visitors until 13:00 will publish at about 13:00, and until then it is shown as missed. Three things make this worse, and the investigation above tells you which one applies. First, full-page caching. When LiteSpeed Cache, an Nginx FastCGI cache, Varnish, or a CDN serves a cached HTML page, the request never reaches PHP. The visitor gets the page, but wp_cron() never runs. A site can have steady traffic in the access log and still miss schedules, because almost all of that traffic is answered by the cache. This is the same split between cached and uncached requests covered in Why WordPress Admin Is Slow Even When the Frontend Loads Fast, seen from the scheduling side. Second, DISABLE_WP_CRON set to true in wp-config.php with no replacement. This constant stops WordPress from spawning WP-Cron on page loads. It is meant to be paired with a real system cron job. Sites that were migrated, copied from a template, or tuned by a performance guide sometimes keep the constant and lose the cron job. In that case nothing ever runs the queue, no matter how much traffic arrives. Third, a broken loopback. Even when wp_cron() decides to spawn, the request to wp-cron.php can fail: the server cannot resolve its own domain, a firewall or security plugin blocks the request, basic auth protects the site, or the SSL certificate the loopback hits is invalid. wp cron test and the Site Health screen in the dashboard both report loopback problems. This also explains the clue from earlier. Dashboard pages are not served from page cache, so when the owner logs in to check, their own request runs wp_cron(), the overdue event fires, and the post appears. The site was waiting for an uncached visit, and the owner provided one.
The immediate goal is to get stuck content out and confirm the queue drains. With WP-CLI, wp cron event run --due-now runs every overdue event once, including pending publish_future_post events. After it finishes, rerun wp post list --post_status=future and the posts that were past their time should be gone from the list. Without shell access, loading any admin page usually has the same effect, as long as DISABLE_WP_CRON is not set and the loopback works. Publishing the post manually from the editor also works, though it does not touch the other overdue tasks. Plugins that "fix missed schedules" exist, and they generally work by checking for overdue posts on page load. On a cached, quiet site they suffer from the same trigger problem, so treat them as a patch, not a fix.
The durable fix is to stop depending on visitors and let the operating system run WP-Cron on a fixed interval. That takes two changes. Turn off the page-load trigger in wp-config.php, above the line that says to stop editing:
PHP
// Stop spawning WP-Cron on page loads; a system cron job runs it instead.
define( 'DISABLE_WP_CRON', true );
Then add a system cron job, either with crontab -e as the site's user or through the control panel's cron jobs page. Using WP-CLI avoids HTTP, caches, and loopback issues entirely, because it runs PHP directly:
TEXT
*/5 * * * * cd /home/USERNAME/public_html && /usr/local/bin/wp cron event run --due-now >/dev/null 2>&1
Replace the path with the site's real document root and the location of wp on your server (which wp will tell you). If WP-CLI is not available, a common alternative is requesting https://example.com/wp-cron.php?doing_wp_cron with wget or curl on the same schedule. That works, but it goes back through the web server, so a misconfigured cache rule or a broken certificate can still get in the way. The order matters. If you set DISABLE_WP_CRON first and forget the cron job, you have created the second failure mode on purpose. Add the cron job, confirm with wp cron event list that events move forward over the next interval, and only then consider the change done. Run the job as the site's own user, not root, so files created by scheduled tasks keep the right ownership. Five minutes is a reasonable default interval for most sites. Posts will publish within that window rather than on the exact minute, which is usually fine. Sites with time-sensitive jobs can run it every minute; servers hosting many sites may prefer to spread the jobs out so they do not all start at once. When "Missed schedule" shows up, treat it as a trigger problem, not a server outage. Check whether the event is in the queue, whether DISABLE_WP_CRON is set, whether page cache is swallowing every request, and whether the loopback works. On any site that is quiet, heavily cached, or simply important, a real system cron job running WP-Cron is the setup that removes the guesswork.