---
title: "WP-Cron Documentation: Every Function, Constant & CLI"
description: "The WP-Cron documentation WordPress never wrote: wp_schedule_event, wp_doing_cron, spawn_cron, DISABLE_WP_CRON, and every WP-CLI cron command."
url: "https://wpwebhooks.org/blog/wp-cron-developer-reference/"
date: "2026-05-17"
---

# WP-Cron Documentation: Every Function, Constant & CLI

TL;DR

-   WP-Cron is not a real scheduler — it is a function called `wp_cron()` that fires on page load and dispatches a non-blocking loopback to `wp-cron.php`.
-   `wp_doing_cron()` returns true only inside that loopback request (when `DOING_CRON` is defined). Use it to guard long-running callbacks.
-   `DISABLE_WP_CRON` in `wp-config.php` disables the page-load trigger but does not disable `wp-cron.php` itself — that is the foundation of every real-cron fix.

/ Definition

## What is **WP-Cron** and how does it actually fire?

WP-Cron is a userland scheduler written in PHP. It is not a Unix cron daemon. It runs entirely inside WordPress and is triggered by HTTP requests to your site — most commonly by visitor page loads, but also by the WordPress admin, the REST API, and explicit calls to `spawn_cron()`.

The flow on every request: WordPress boots, runs the `wp_cron()` function near the end of `wp_loaded`, checks the `cron` option in the database for events whose `time` field is in the past, and — if any are due — calls `spawn_cron()` to fire a non-blocking `wp_remote_post()` loopback request against `wp-cron.php`. The visitor's response is returned without waiting for the cron run to finish.

The canonical handler is `wp-cron.php` at the WordPress root. It defines the `DOING_CRON` constant, loads WordPress, iterates the due events, and runs `do_action( $hook, $args )` for each.

See the [official WP-Cron handbook](https://developer.wordpress.org/plugins/cron/) for the canonical description of this lifecycle.

/ wp\_doing\_cron()

## What does **wp\_doing\_cron()** return and when do you use it?

It returns `true` when the current PHP request is the cron loopback — that is, when the `DOING_CRON` constant has been defined. It returns `false` during normal visitor requests, admin requests, REST requests, and WP-CLI commands (except for `wp cron event run`, which sets `DOING_CRON` manually).

Use it to guard long-running work that should never block a user-facing request. A common pattern: an `add_action('my_event', ...)` callback that does file processing wraps its body in `if ( wp_doing_cron() )` as a defensive check, so if the hook is ever fired in another context the work is skipped instead of stalling the request.

Internally it is a thin wrapper around `wp_doing_cron()` applies the filter of the same name. The signature is `wp_doing_cron(): bool`. See the [wp\_doing\_cron() reference](https://developer.wordpress.org/reference/functions/wp_doing_cron/).

wp\_doing\_cron() and spawn\_cron() in practice

```
// Inside any request that wants to trigger overdue cron events.
if ( ! wp_doing_cron() && _get_cron_array() ) {
    spawn_cron(); // fires a non-blocking loopback to /wp-cron.php?doing_wp_cron
}

// Inside a callback registered with add_action('my_cron_event', ...).
function my_cron_callback() {
    if ( wp_doing_cron() ) {
        // Safe to do long-running work — no user is waiting on this PHP request.
    }
}
```

/ spawn\_cron()

## How does **spawn\_cron()** work under the hood?

It fires a `wp_remote_post()` request to `site_url('/wp-cron.php?doing_wp_cron=<timestamp>')` with `blocking => false`. The current request continues immediately; the loopback request runs in a separate PHP process. This is the mechanism that lets the user-facing response return quickly even when cron has work to do. See the [spawn\_cron() reference](https://developer.wordpress.org/reference/functions/spawn_cron/).

Before firing, `spawn_cron()` writes a `doing_cron` transient with the same timestamp. This is a one-minute soft lock — a second concurrent visitor will see the transient is set, will not call `spawn_cron()` again, and will trust that the existing loopback is in progress. The transient prevents a swarm of overlapping cron runs on a busy site.

It is safe to call `spawn_cron()` from your own code if you want to force-fire overdue events without waiting for the next page load. Wrap it in `if ( ! wp_doing_cron() )` to avoid recursive loopbacks.

/ DISABLE\_WP\_CRON

## What does **DISABLE\_WP\_CRON** actually disable?

Defined as `define( 'DISABLE_WP_CRON', true );` in `wp-config.php`, it suppresses the page-load trigger only. The check happens inside [`wp_cron()`](https://developer.wordpress.org/reference/functions/wp_cron/) — if the constant is truthy, the function exits immediately without spawning anything. Visitor requests stop firing cron.

It does _not_ disable `wp-cron.php` itself. The script remains publicly accessible and still iterates due events when hit directly. This is the entire point: pair `DISABLE_WP_CRON` with a system crontab entry that hits `wp-cron.php` every minute, and you get reliable time-based execution decoupled from traffic.

It also does not disable scheduling — `wp_schedule_event()` still writes to the `cron` option, plugins still register their schedules, and the events still execute when `wp-cron.php` runs. The constant only affects _when_ cron runs, not _whether_ it runs.

Common mistake: setting `DISABLE_WP_CRON` without configuring a real cron. The page-load trigger is gone, no system cron exists, so `wp-cron.php` never runs and every scheduled event silently stops firing. Always pair them — see [the system cron setup guide](/blog/cron-job-for-wordpress/).

/ WP-CLI

## Which **WP-CLI commands** run a real cron sweep?

[`wp cron event run --due-now`](https://developer.wordpress.org/cli/commands/cron/event/run/) is the workhorse. It runs every event whose `time` is in the past, in the current PHP process (no loopback), with `DOING_CRON` defined so `wp_doing_cron()` behaves correctly inside callbacks. This is what you put in your system crontab.

Use `wp cron event run <hook>` to fire a single named event on demand — useful during plugin development and when investigating a single misbehaving callback. Use `wp cron event list` for inventory: every registered event, its next-run time, recurrence interval, and the hook name. `wp cron test` issues a loopback to `wp-cron.php` and reports whether the server can reach itself (a common failure on managed hosts with strict outbound firewalls).

WP-CLI cron commands — the daily-driver subset

```
# List every scheduled event.
wp cron event list

# Run every due event right now, in this PHP process (no loopback).
wp cron event run --due-now

# Run one specific hook on demand.
wp cron event run my_cron_event

# Show registered schedules (hourly, twicedaily, daily, plus custom).
wp cron schedule list

# Check whether wp-cron.php is reachable from the server itself.
wp cron test
```

/ Real Cron

## How do you **replace WP-Cron with a real system cron**?

Two lines of configuration. First, in `wp-config.php` above the `/* That's all, stop editing! */` comment: `define( 'DISABLE_WP_CRON', true );`. Second, in the system crontab for the web user (commonly `www-data` on Debian/Ubuntu):

/etc/cron.d/wordpress — guaranteed one-minute execution

```
# Runs every minute regardless of site traffic. WP-CLI variant preferred when available.
* * * * * www-data /usr/local/bin/wp --path=/var/www/site cron event run --due-now >/dev/null 2>&1

# Curl fallback if WP-CLI is not installed:
* * * * * www-data curl -s https://your-site.com/wp-cron.php?doing_wp_cron >/dev/null 2>&1
```

The WP-CLI form is preferred because it runs in-process, captures errors directly, and avoids the HTTP loopback (which can be blocked, throttled, or rate-limited by upstream proxies). The curl form is the universal fallback and works on shared hosting where you cannot install WP-CLI.

/ Scheduling API

## Where do **wp\_schedule\_event** and **wp\_schedule\_single\_event** fit?

[`wp_schedule_event( $timestamp, $recurrence, $hook, $args, $wp_error )`](https://developer.wordpress.org/reference/functions/wp_schedule_event/) registers a recurring event. `$recurrence` must match a registered schedule name (`hourly`, `twicedaily`, `daily`, `weekly` in core; `cron_schedules` filter to add custom). It writes to the `cron` option and is idempotent only when `$args` match exactly — different args produce different scheduled events.

[`wp_schedule_single_event( $timestamp, $hook, $args )`](https://developer.wordpress.org/reference/functions/wp_schedule_single_event/) registers a one-shot event. It deduplicates within a ten-minute window: scheduling the same hook + args twice within ten minutes produces only one event. Use it as the WordPress-native pattern for "do this work later, off the request path."

Pair both with `add_action( $hook, $callback )` in the same execution path that will run cron — typically the same plugin file, so the callback is registered every time WordPress loads.

/ vs Action Scheduler

## How is **WP-Cron different from Action Scheduler**?

| Capability | WP-Cron | Action Scheduler |
| --- | --- | --- |
| Storage | cron option (autoloaded) | Dedicated DB tables |
| Scale ceiling | ~100 events before option bloat | 100k+ actions per day |
| Concurrency | Single loopback per tick | Configurable concurrent batches |
| Failure recovery | Silent — event just doesn't run | Per-action status + retry |
| Observability | No log of past runs | Status > Scheduled Actions UI |
| Trigger | Page load or system cron | Same — uses WP-Cron underneath |

Action Scheduler is built on top of WP-Cron — its queue runner is itself a scheduled cron event. Replacing WP-Cron with a system cron makes Action Scheduler reliable; the two work together rather than competing. See [the Action Scheduler concurrent batches reference](/blog/action-scheduler-concurrent-batches-filter/) for the throughput tuning that depends on a reliable cron firing the queue runner.

## Structured data

```json
{"@context":"https://schema.org","@type":"Article","headline":"WP-Cron Documentation: Every Function, Constant & CLI","description":"The WP-Cron documentation WordPress never wrote: wp_schedule_event, wp_doing_cron, spawn_cron, DISABLE_WP_CRON, and every WP-CLI cron command.","datePublished":"2026-05-17","dateModified":"2026-05-17","author":{"@type":"Person","name":"Mateusz Skorupa","url":"https://wpwebhooks.org/about/"},"publisher":{"@type":"Organization","name":"WP Webhooks","url":"https://wpwebhooks.org"},"url":"https://wpwebhooks.org/blog/wp-cron-developer-reference/","image":"https://wpwebhooks.org/og_image.jpg","keywords":["wordpress wp cron official documentation","wp doing cron","spawn cron","disable wp cron","wp cron event run due now","what is wp cron","wp cron developer reference"]}

{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"WP Webhooks","item":"https://wpwebhooks.org/"},{"@type":"ListItem","position":2,"name":"Blog","item":"https://wpwebhooks.org/blog/"},{"@type":"ListItem","position":3,"name":"WP-Cron Documentation: Every Function, Constant & CLI","item":"https://wpwebhooks.org/blog/wp-cron-developer-reference/"}]}

{"@context":"https://schema.org","@type":"FAQPage","mainEntity":[{"@type":"Question","name":"What is WP-Cron in plain terms?","acceptedAnswer":{"@type":"Answer","text":"WP-Cron is a PHP function inside WordPress that pretends to be a scheduler. It fires on page load — when a visitor hits any page, WordPress checks for overdue scheduled events and runs them in a separate background HTTP request. It is not a real Unix cron daemon."}},{"@type":"Question","name":"What does wp_doing_cron() return outside of the cron loopback?","acceptedAnswer":{"@type":"Answer","text":"It returns false. wp_doing_cron() returns true only when the DOING_CRON constant is defined — which happens inside wp-cron.php and inside wp cron event run. In normal visitor requests, admin pages, and REST requests it returns false."}},{"@type":"Question","name":"Does DISABLE_WP_CRON disable wp_schedule_event?","acceptedAnswer":{"@type":"Answer","text":"No. DISABLE_WP_CRON only disables the page-load trigger. wp_schedule_event still writes events to the cron option, plugins still register schedules, and the events still execute when wp-cron.php is hit directly or by WP-CLI."}},{"@type":"Question","name":"What does wp cron event run --due-now do?","acceptedAnswer":{"@type":"Answer","text":"It runs every scheduled event whose time is in the past, in the current PHP process, with DOING_CRON defined. No HTTP loopback is fired. This is the WP-CLI command you put in your system crontab for reliable execution."}},{"@type":"Question","name":"Why doesn't my scheduled event ever fire?","acceptedAnswer":{"@type":"Answer","text":"Three common causes: DISABLE_WP_CRON is set without a system cron, so wp-cron.php never runs; the site has near-zero traffic and the page-load trigger never fires; or the callback was registered with add_action in a code path that does not load during the cron request. Run wp cron event list to confirm the event is scheduled, then wp cron event run hook_name to confirm the callback executes."}}]}
```
