Every WP-Cron function, constant, and WP-CLI command developers actually search for — with the exact signature, the production gotcha, and the link to the canonical Code Reference.
TL;DR
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.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.
Visitor request
│
▼
WordPress boot ── wp-includes/default-filters.php
│
├─ DISABLE_WP_CRON defined truthy? ──► skip, do not spawn
│
▼
wp_cron() reads cron array (option: cron)
│
├─ no events due ──► return, end request
│
▼
spawn_cron()
│
▼
wp_remote_post( wp-cron.php?doing_wp_cron, blocking=false )
│
▼
── visitor request ends ──
│
(separate PHP request)
▼
wp-cron.php
│
├─ define( 'DOING_CRON', true )
├─ load WordPress
├─ for each due event:
│ do_action( $hook, $args )
└─ exit
See the official WP-Cron handbook for the canonical description of this lifecycle.
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.
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.
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.
Defined as define( 'DISABLE_WP_CRON', true ); in wp-config.php, it suppresses the page-load trigger only. The check happens inside 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.
wp cron event run --due-now 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).
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):
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.
wp_schedule_event( $timestamp, $recurrence, $hook, $args, $wp_error ) 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 ) 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.
| Capability | WP-Cron | Action Scheduler |
|---|---|---|
cron option (autoloaded) | Dedicated DB tables | |
| ~100 events before option bloat | 100k+ actions per day | |
| Single loopback per tick | Configurable concurrent batches | |
| Silent — event just doesn't run | Per-action status + retry | |
| No log of past runs | Status > Scheduled Actions UI | |
| 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 for the throughput tuning that depends on a reliable cron firing the queue runner.
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.false.
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.time is in the past, in the current PHP process, with DOING_CRON defined. No HTTP loopback is fired.* * * * * www-data wp --path=/var/www/site cron event run --due-now.
DISABLE_WP_CRON is set without a system cron, so wp-cron.php never runs.add_action() in a code path that does not load during the cron request.wp cron event list to confirm the event is scheduled, then wp cron event run <hook_name> to confirm the callback executes.