Ahosting Logo
Knowledge Base

WP-Cron: Why Scheduled Posts Do Not Publish

Why WordPress scheduled tasks are unreliableWordPress checks for due taskswhen somebody visits the siteA quiet site has no visitorsso nothing is checked, and nothing runsThe post publishes latewhenever the next visitor happens to arriveThe replacement is a real cron job calling wp-cron.php, and the WordPress trigger must be disabled at the same time orboth run.

WordPress schedules its own tasks: publishing scheduled posts, checking for updates, running plugin jobs, sending queued email. What surprises people is how it decides when to run them: it checks whether anything is due only when someone visits the site.

On a busy site that is close enough to a schedule. On a quiet site it means scheduled posts publish late, backups do not run, and nothing reports it.

Why this design exists

WordPress has to work on any hosting, including plans with no access to real scheduled tasks. Triggering on page loads is the only mechanism guaranteed to be available everywhere.

The trade is accuracy. A post scheduled for 09:00 publishes at 09:00 only if somebody loads a page at 09:00. If the next visitor arrives at 14:00, that is when it publishes, and the post shows its original scheduled time, which makes the delay easy to miss.

The symptoms

Scheduled posts publish late, or show "Missed schedule" and never publish at all.

Backup plugins do not run on their schedule, and the plugin reports the job as pending rather than failed.

Queued email is delayed: order confirmations, notifications, anything a plugin queues rather than sending immediately.

All three look like a broken plugin and are not.

The opposite problem on a busy site

The same mechanism causes the reverse failure under traffic.

Every page load checks whether tasks are due, and on a busy site that check runs constantly. If a task is heavy, it runs during a visitor's page load: that visitor waits for it, and the process it occupies is one of the small number your plan allows.

So on a busy site WP-Cron is a performance cost, and on a quiet one it is unreliable. The fix is the same for both.

Replace it with a real scheduled task

Two steps, and doing only the first is the mistake that matters.

Step one. Turn off the visitor-triggered version in wp-config.php, above the line telling you to stop editing:

define( 'DISABLE_WP_CRON', true );

Step two. Create a real cron job that calls it on a schedule. Every fifteen minutes suits most sites:

*/15 * * * * /usr/local/bin/php -q /home/username/public_html/wp-cron.php >/dev/null

Use the full path to both the PHP binary and the file: cron does not start in your website directory and does not inherit your shell's PATH. And redirect the output, or every run emails you.

Setting up cron jobs in cPanel goes over the scheduling screen.

Doing only step one is worse than doing nothing

Disabling the built-in version without adding the replacement stops scheduled tasks entirely.

Nothing errors. Scheduled posts stop publishing, backups stop running, queued email stops sending, and the dashboard looks completely normal. The symptom surfaces days later and nobody connects it to a line added to a configuration file.

If you take one thing from this page, it is that these two steps are a pair.

Confirm it is working

Schedule a post for five minutes from now and wait. If it publishes on time without you loading the site, the replacement is running.

A scheduling plugin that lists pending tasks is also worth installing once. It shows what is queued and when each item last ran, which answers "is cron working" directly rather than by inference.

Do not run it every minute

Each run starts PHP and consumes one of the processes your plan allows. A heavy task on a one-minute schedule competes with your visitors for the same capacity.

Fifteen minutes is right for most sites. If something genuinely needs to happen at an exact minute, that is a task for a dedicated cron job calling the specific thing, rather than running the whole WordPress task queue more often.

If tasks still do not run

Check the four things in order: is DISABLE_WP_CRON actually set, is the cron job present, does the command work when you run it by hand, and is the path to PHP correct.

That last one is the usual answer. The PHP binary path differs between accounts and versions, and a wrong path produces a cron job that runs and does nothing.

Find out what is actually scheduled

Before changing anything, look at what the site is trying to run. The list is frequently the explanation.

wp cron event list --fields=hook,next_run_relative,recurrence
wp cron event list | wc -l

Two findings are common and both matter.

Hundreds of entries. Usually one plugin scheduling an event per item: per product, per subscriber, per order. Every visit then triggers a check against all of them, which is a real cost on the visitor's page load.

Events from plugins that are no longer installed. Removing a plugin does not always clear its scheduled events, so the site keeps trying to run a hook nothing implements. Harmless individually and part of the accumulation.

Using WP-CLI goes over reaching these commands, and clearing an obsolete event is wp cron event delete.

An event that is overdue rather than missing

An event whose next run is in the past is not necessarily broken. WordPress runs it on the next visit, so on a quiet site a task scheduled for 02:00 genuinely runs at 09:14 when somebody arrives.

That is the design working, and it is why "the backup did not run at the scheduled time" is usually not a fault at all. It is a site with no traffic at two in the morning.

Where the timing genuinely matters, the system cron replacement is the fix in place of any adjustment to the schedule.

When one task blocks the rest

Scheduled events run in sequence within a single request. A task that takes a long time. A large export, an image regeneration, a slow external request, can exhaust the PHP time limit before the queue is finished.

Everything after it in the queue never runs, and there is no error anywhere. The symptom is a set of unrelated features that all stopped working at once, which is rarely connected to a single slow job.

The diagnosis is to look at what sits at the front of the queue and how long it takes. The fix is usually to make that task incremental, or to move it out of the queue entirely.

The store version of this problem

WooCommerce runs its own background system on top of this, and it is where order emails, stock updates and webhook deliveries live.

When it stalls, the store keeps taking orders normally and stops doing everything around them: confirmations not sent, integrations not notified, stock not adjusted. Nothing reports a failure because nothing failed; the work is simply queued.

Several unrelated store features breaking together is the signature, and it is worth checking before investigating any of them individually, webhooks and integrations deals with what fails silently, and WooCommerce database growth explains the rows a stalled queue leaves behind.