Independent software, built & maintained by PRBLEM.
28 releases 21 free · 7 professional STORE / 2.41.0
PRBLEMindependent software
Home Field Notes WordPress
WordPress FIELD NOTE

WP-Cron Is Not a Real Cron Job — And That Matters More Than You Think

WP-Cron is a scheduling mechanism, but its default execution model is request-driven. That difference explains many mysterious delayed tasks.

PRBLEM — XenForo add-ons, WordPress plugins and software tools FIELD NOTE // 9003

WordPress has scheduled events, recurring hooks and timestamps that
look very much like a traditional cron system.

But the default execution model is different.

A system cron daemon wakes up because the operating system tells it
to. WP-Cron normally gets an opportunity to run because WordPress
receives traffic and notices that scheduled work is due.

That difference is responsible for a large class of “it was
scheduled, so why didn’t it happen?” problems.

Scheduling and
execution are different things

When WordPress schedules an event, it records that an action should
become due at a timestamp.

That timestamp is not a guarantee that code will execute at that
exact second.

Conceptually:

12:00:00  event becomes due
12:00:00  no request arrives
12:04:37  visitor requests a page
12:04:37  WordPress gets a chance to spawn cron processing
12:04:38  event may execute

The event is late, but the scheduler is behaving according to its
request-driven model.

This is why a small delay is not automatically evidence of
failure.

Low-traffic sites expose the
model

On a high-traffic site, requests arrive constantly, so due events
usually get frequent opportunities to run.

On a staging site, internal portal, new blog or low-traffic store,
there may be long periods with no requests.

Tasks can remain due until traffic returns.

That can affect:

  • scheduled publishing;
  • update checks;
  • cleanup tasks;
  • plugin maintenance;
  • email digests;
  • queue dispatching;
  • subscription-related operations.

The visible site can look completely normal.

“Overdue” needs context

A cron diagnostic should not label every past-due timestamp as
catastrophic.

Suppose a hook is intended to run hourly and is eight minutes late.
On request-driven scheduling, that may be normal.

Suppose the same hook has not progressed in 18 hours on a site
receiving constant traffic. That is much more interesting.

Useful signals include:

  • how long the event has been due;
  • expected recurrence;
  • whether the callback still exists;
  • whether many events are overdue at once;
  • whether cron spawning is disabled;
  • whether loopback requests are failing;
  • whether a large queue is blocking later work.

A diagnosis should report the evidence instead of collapsing it into
one red “broken” label.

DISABLE_WP_CRON
changes the contract

Production sites often disable request-triggered WP-Cron:

define('DISABLE_WP_CRON', true);

This can be a good architecture when a real system scheduler is
configured to trigger WordPress predictably.

But the constant alone does not configure that replacement.

If WP-Cron is disabled and no external scheduler invokes it,
scheduled work can simply stop.

That is why a useful readiness check should distinguish:

WP-Cron disabled + external trigger present

from:

WP-Cron disabled + no evidence of replacement

The second state deserves attention.

Using a real system cron

A common approach is to let the operating system call WordPress every
few minutes.

For example, an environment may invoke wp-cron.php over
HTTP:

*/5 * * * * curl -fsS https://example.com/wp-cron.php?doing_wp_cron >/dev/null 2>&1

Or use WP-CLI locally when available:

*/5 * * * * cd /var/www/example && wp cron event run --due-now --quiet

The exact command depends on hosting, permissions and deployment
architecture.

The important property is deterministic opportunity: the scheduler
gets invoked even when no visitor arrives.

Do not create
overlapping execution accidentally

More frequent triggering is not automatically better.

If a task can take several minutes and a system cron starts another
execution every minute, you may create overlapping work unless the
application or job provides locking.

That can produce:

  • duplicate emails;
  • concurrent imports;
  • lock contention;
  • duplicated remote requests;
  • extra database load.

A proper schedule considers runtime, idempotency and locking.

WP-Cron events are hooks

At a high level, scheduled WordPress events invoke hooks. Plugins
attach callbacks to those hooks.

An event can still exist after the code that registered its callback
is gone or disabled.

That is why callback availability is useful diagnostic evidence.

A scheduled hook with no active callback may be harmless leftover
state, or it may explain why expected work is not occurring.

Do not automatically delete it. Identify ownership first.

WooCommerce and many other plugins use Action Scheduler for
background work.

It provides a queue-oriented model with statuses such as pending,
complete, failed and in-progress. WordPress cron is often involved in
initiating processing, but the queue has its own operational state.

This means a site can have two distinct questions:

  1. Is WP-Cron getting opportunities to run?
  2. Is the Action Scheduler queue draining successfully?

A growing pending queue or a persistent set of failed actions can be
more informative than the top-level WP-Cron state.

What to inspect
when scheduled work is late

Start with evidence.

1. Is WP-Cron disabled?

Check configuration and hosting controls.

2. Is there an external
trigger?

Look at system cron, hosting scheduler or WP-CLI automation.

3. How old are the overdue
events?

One slightly late event is different from dozens of events overdue by
hours.

4. Do the callbacks still
exist?

Disabled or removed plugins can leave scheduled hooks behind.

5. Is a queue involved?

Inspect Action Scheduler or the plugin’s own queue if applicable.

6. Are loopback requests
healthy?

Request-driven cron can be affected by HTTP restrictions,
authentication, DNS or server configuration.

7. Are long-running
tasks blocking progress?

Check logs, failures and runtime.

What not to do first

Do not start by deleting every overdue event.

Do not randomly increase cron frequency.

Do not repeatedly hit wp-cron.php from multiple external
services.

Do not assume a failed action is safe to rerun without understanding
whether the operation is idempotent.

The safest first step is to observe what is due, what owns it and
whether progress is being made.

A useful operational
definition of “healthy”

For scheduled work, health is not “no event timestamp is in the
past.”

A more useful definition is:

Expected recurring work progresses within a bounded delay, callbacks
remain available, and background queues do not accumulate unbounded
pending or failed work.

That definition respects the way WordPress actually schedules
tasks.

WP-Cron is not a traditional operating-system cron daemon.

Once you design diagnostics around that fact, delayed tasks become
much easier to reason about.