Architecture Note: Decoupling WordPress WP‑Cron from Visitor Traffic
Learn why WP‑Cron’s traffic‑dependent execution is fragile, how to replace it with a system scheduler, and what to check before and after the change.
19 Apr 2026, 15:20 UTC

Requirements
WordPress sites need reliable background work: publishing future‑dated posts, checking for core/plugin/theme updates, sending queued emails, and cleaning temporary data. WP‑Cron satisfies this by storing scheduled events in the cron option (a serialized array in the wp_options table) and executing the hooks whenever wp-cron.php runs.
Smallest Suitable Design
Keep WP‑Cron’s storage and hook model but move its execution out of the request cycle. Add one line to wp-config.php to disable the built‑in spawning, then invoke wp-cron.php from a system scheduler (cron) every few minutes, or use WP‑CLI’s wp cron event run --due-now. This requires only:
define('DISABLE_WP_CRON', true);inwp-config.php.- A system‑cron entry such as
*/5 * * * * www-data /usr/local/bin/php /path/to/wordpress/wp-cron.php >/dev/null 2>&1.
If you prefer WP‑CLI, replace the PHP call with wp cron event run --due-now --path=/path/to/wordpress.
Trust and Data Boundary
wp-cron.php is an HTTP(S) entry point that runs with full WordPress privileges and executes any hook a plugin has scheduled. After disabling the built‑in spawn, treat the file as a privileged script that must only be reachable by your trusted scheduler. Recommended steps:
- Deny direct web access to
wp-cron.php(e.g., vianginxlocation ~ wp-cron.php { deny all; }or an.htaccessrule). - If you keep the default spawning for any reason, ensure loopback requests (
127.0.0.1or::1) are allowed through firewalls and TLS interception.
Operational Checks
Verify that the scheduler is working and that WordPress sees the events:
- List scheduled events:
wp cron event list --format=table --path=/path/to/wordpress(run as the WordPress file‑system user, e.g.,www-data). - Run due events manually:
wp cron event run --due-now --path=/path/to/wordpressand confirm the output shows processed hooks. - Check the scheduler’s log (e.g.,
/var/log/syslogorjournalctl -u cron) for the cron entry’s success. - Watch the WordPress admin under Tools → Site Health for any “loopback request” warnings when the default spawn is disabled; these should disappear.
- Inspect the raw cron option:
wp option get cron --format=json --path=/path/to/wordpressto see timestamps and verify they advance after each scheduler run.
Failure Modes
Even with a system scheduler, be aware of these residual risks:
- Overlapping runs: If the scheduler interval is shorter than the longest WP‑Cron job, a new invocation may start while the previous one is still executing, causing duplicate work. Mitigate by ensuring the interval exceeds the worst‑case runtime or by using a lock file in the scheduler entry.
- Missed lock: WP‑Cron still uses an internal lock (
WP_CRON_LOCK_TIMEOUT, default 60 s). If a job holds the lock longer than the scheduler interval, subsequent runs become no‑ops until the lock releases. - Scheduler failure: If the system cron daemon stops, all scheduled work halts silently. Monitor the cron service with your usual host‑level alerts.
- Multisite nuance: In a multisite installation, each site has its own
cronoption. A single scheduler call towp-cron.phpprocesses the current site only; you must either loop through sites or use WP‑CLI’swp site list --field=urlto invokewp cron event run --due-nowper site.
Conditions That Would Trigger a Redesign
Consider moving beyond the simple scheduler if:
- You have a large, predictable queue (e.g., bulk email, import jobs) that benefits from a dedicated worker loop such as Action Scheduler with its own daemon.
- Your hosting environment (serverless functions, containers) makes reliable loopback or system cron difficult; a CLI‑driven approach triggered by the platform’s native scheduler may be preferable.
- You observe frequent overlapping runs or lock‑timeout issues despite tuning the interval; a dedicated queue system can serialize work more predictably.
Practical Verification on a Staging Copy
- Copy the production site to a staging subdomain or directory.
- Add
define('DISABLE_WP_CRON', true);towp-config.php. - Create a test scheduler entry:
*/1 * * * * www-data /usr/local/bin/php /path/to/staging/wp-cron.php >/dev/null 2>&1(runs every minute). - Schedule a post to publish one minute in the future via the WordPress editor.
- Wait two minutes, then check the post’s status. If it published, the scheduler is working.
- Run
wp cron event list --path=/path/to/stagingbefore and after manually invokingwp cron event run --due-now --path=/path/to/stagingto confirm events are cleared. - Review Tools → Site Health for any cron‑related notices; they should be absent.
Limitations and How to Check Results
The scheduler approach does not eliminate WordPress’s internal lock mechanism; long‑running tasks will still block subsequent runs until the lock expires. To verify that a task is not being blocked:
- Measure the typical runtime of your heaviest WP‑Cron hook (e.g., a backup script) using
time wp cron event run --due-now --hook=your_hook --path=/path/to/wordpress. - Ensure the scheduler interval is set to at least that measured runtime plus a safety margin (e.g., 5 minutes).
- If you still see missed executions, increase the interval or implement an external lock (e.g.,
flock) around the scheduler call.
Rollback (Re‑Enabling Default WP‑Cron)
Because disabling WP‑Cron changes site state, you can revert by:
- Removing or commenting out the
define('DISABLE_WP_CRON', true);line inwp-config.php. - Deleting the system‑cron entry you added.
- If you denied web access to
wp-cron.php, restore the previous web‑server rule to allow loopback requests. - Verify that the Site Health screen now shows the loopback test as “good” and that scheduled events resume on page traffic.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.