2 sites for $49.99 ·

AcceleratorWP
All posts
Performance

How to Disable wp-cron Without Breaking Scheduled Posts (2026)

The standard "set DISABLE_WP_CRON to true" advice silently breaks scheduled posts, WooCommerce emails, abandoned-cart reminders, and half the cron-dependent plugins on a real site. Here's the version that actually ships.

Sarp EfeMay 14, 202616 min read

Every WordPress performance tutorial gets to the same step around the middle. "Disable wp-cron in your wp-config." Drop in a one-liner, the site gets faster, you move on. A week later someone in marketing asks why the scheduled product launch didn't go live at 09:00. Three weeks later a customer emails because they never got their order confirmation. A month later you find out your nightly backup hasn't run since the day you copied that one-liner.

The advice isn't wrong. The advice is half a fix. The other half — the part that keeps your site's scheduled work running — is what almost no tutorial bothers to explain in enough detail to actually deploy.

Quick disclosure I always include: I run AcceleratorWP, a plugin whose Cron Decoupler module does this dance for you. I'll mention it once at the end. Everything before that stands alone.

A wall clock at 4:13 AM in an empty office — the kind of hour your cron jobs are supposed to be running, but probably aren't
A wall clock at 4:13 AM in an empty office — the kind of hour your cron jobs are supposed to be running, but probably aren't

What people Google, and what they actually want

The query is one of these:

  • "disable wp-cron"
  • "wp-cron slow"
  • "wp-cron.php high cpu"
  • "wordpress site slow because of cron"
  • "DISABLE_WP_CRON not working"

Underneath all of them is one observation. The site is slow, the host's CPU graph has spiky peaks aligned with traffic bumps, and every guide says wp-cron is the culprit. The fix appears to be one line in wp-config.php. Done. Except the fix is one third of the answer; the rest is the part that decides whether your scheduled posts publish tomorrow morning.

How wp-cron actually works (the bit nobody explains)

WordPress doesn't have a real cron daemon. It has a fake one — a wp-cron.php script that runs in-process during page requests. On every uncached front-end hit, WordPress checks whether any scheduled tasks are due, and if they are, it fires off a non-blocking HTTP request back to /wp-cron.php on the same host to run them.

That extra HTTP request is what's eating your TTFB. It's not just the time spent running scheduled tasks (which can be 100ms to several seconds depending on what's scheduled). It's the overhead of the additional internal request, the lock contention when multiple visitors race to fire it at the same time, and the dogpile of duplicate cron dispatches on traffic spikes.

On a low-traffic site this is barely visible. On a site with even 200 visits/hour, wp-cron is firing 50–200 times an hour. On a Black Friday-style spike with 50 concurrent visitors, you can have a dozen wp-cron requests racing each other before the first one finishes its scheduled tasks. The host's CPU climbs, page loads slow, and the cron tasks themselves start running multiple times in parallel — which is its own category of bug (double-sent emails, double-charged subscriptions, half-applied database migrations).

The real fix is to stop wp-cron from running on visitor requests, and run it on a real, predictable schedule from outside WordPress. The first half of that is one config line. The second half is where everyone gets it wrong.

Here's what every guide tells you to do. Add this to wp-config.php:

define('DISABLE_WP_CRON', true);

Save. Done. The site is faster.

It is faster. It's also broken in ways you won't notice for one to four weeks. In rough order of how often I've seen each on real client sites:

1. Scheduled posts don't publish

The most common symptom and the easiest to spot. Editors schedule a post for 09:00 Tuesday. Tuesday 09:00 comes and goes. The post stays on "Scheduled" status. Twenty minutes later someone refreshes the front page and it appears — but only if there's traffic, and only because that visitor triggered the now-disabled wp-cron path which... was disabled. So actually the post never publishes until someone notices and hits Publish manually.

What's happening: WordPress's scheduled-post system is a cron job. publish_future_post fires on a schedule, and the schedule is wp-cron. Disable wp-cron, scheduled posts wait forever.

2. WooCommerce emails and order processing stall

On WooCommerce sites the symptom is more expensive. Order confirmation emails come in late or not at all. Abandoned-cart reminders never fire. Subscription renewals don't generate new orders. Stock sync to the warehouse stops. The store runs — orders get placed, payments process — but everything that's supposed to happen after the order silently doesn't.

WooCommerce moved its background work to Action Scheduler years ago, which is a more sophisticated cron system. It still depends on wp-cron firing periodically to dispatch queued actions. Disable wp-cron without anything taking its place, and Action Scheduler's queue just grows. The "Scheduled actions" admin page fills with hundreds of pending entries dated three weeks ago.

3. Backup plugins stop running

UpdraftPlus, BackWPup, BlogVault — every backup plugin schedules its work through wp-cron. The last successful backup quietly stays the last successful backup. You won't find out until you need it.

This is the silent regression I lost the most sleep over. A client called about a database corruption six weeks after we'd disabled wp-cron and forgotten to set up the system cron half. The most recent UpdraftPlus backup was from the day we'd made the change. The five intervening weeks of orders were gone.

4. Security scans, sitemap rebuilds, and everything else

Wordfence malware scans, Sucuri integrity checks, Yoast sitemap regeneration, Rank Math indexing API submissions, MailPoet newsletter dispatches, Gravity Forms scheduled exports, membership drip content, license heartbeats — all of it runs on wp-cron. All of it silently stops.

The recurring shape of every regression: there's no error, no warning, nothing visible in the admin. The plugin's status page often still shows "Active" because the plugin itself is fine. The scheduler is broken, and most plugins don't render a "your cron isn't running" warning because most plugin authors assume wp-cron is working.

The honest mental model

There are two things to do, in order, and both have to happen for the fix to be safe:

  1. Stop wp-cron from running on visitor requests. This is the line in wp-config.php. It's the half that gives you the TTFB win.
  2. Run wp-cron from somewhere else, on a real schedule. This is the half that keeps your scheduled work alive. It's a system cron, a host-managed cron, or an external service that hits /wp-cron.php?doing_wp_cron on a fixed interval.

The popular advice does step 1 and stops there. The version that ships does both.

What actually works in production

Step 1 — Disable the in-process trigger

In wp-config.php, before the That's all, stop editing! comment:

define('DISABLE_WP_CRON', true);

That stops WordPress from firing wp-cron on visitor requests. Nothing else changes about how cron would run — the scheduled tasks are still there, the cron table in the database is still populated, plugins still register their hooks. The trigger mechanism just goes idle.

Step 2 — Set up an external trigger

Now pick one of the three ways to hit /wp-cron.php on a real schedule. Which one depends on what your hosting gives you.

Server cron via SSH (the cleanest option). If you have SSH access — most VPS hosts, Dokploy / Coolify / Ploi-managed instances, dedicated servers, anything that isn't a pure managed-WP service — add a line to crontab:

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

That hits wp-cron.php every five minutes. The doing_wp_cron query parameter is what WordPress checks to know it's a legitimate cron dispatch (the value can be empty; the presence of the parameter is what matters). The > /dev/null 2>&1 discards output so your cron logs don't fill up with WordPress's HTML.

Five minutes is the sweet spot. Anything less than one minute starts to overlap with itself on slow sites. Anything more than fifteen minutes makes scheduled posts feel delayed.

WP-CLI variant for hosts that prefer command-line over HTTP:

*/5 * * * * cd /path/to/wp && wp cron event run --due-now > /dev/null 2>&1

This skips the HTTP round-trip and runs due events directly through WP-CLI. Faster, more reliable, requires WP-CLI on the host.

Managed hosting cron panel. Kinsta, WP Engine, SiteGround, Cloudways, Pressable — every managed WP host has a cron panel in their dashboard. Most of them auto-configure a system cron that calls wp-cron.php every minute or two when you flip DISABLE_WP_CRON to true. Read your host's docs; many of them handle step 2 automatically and the only thing you need to do is step 1. (Kinsta and WP Engine in particular do this transparently.)

External cron service. If your host has no SSH and no built-in cron panel — some entry-level shared hosts fit this — use a free service like cron-job.org or EasyCron. They'll hit your wp-cron.php URL on a schedule from outside. The trade-offs: rate-limited at the free tier, dependent on a third party staying up, and the request looks like external traffic in your access logs.

Step 3 — Verify it's actually running

Don't trust that it's working. On a WooCommerce site, install the free WP Crontrol plugin and look at the Tools → Cron Events page. Every event has a "Next Run" timestamp. After your external trigger has fired once, the timestamps for due events should advance.

On stores using Action Scheduler, also check WooCommerce → Status → Scheduled Actions. Pending actions should be flowing through to Completed within a few minutes of their scheduled time. If you see a Pending action whose scheduled time was an hour ago and the status hasn't changed, the external trigger isn't reaching Action Scheduler's runner.

A practical thing I do on every install: schedule a fake cron event for two minutes in the future and watch it fire. Five lines in a mu-plugin you can delete after the test:

add_action('init', function () {
    if (!wp_next_scheduled('my_cron_test')) {
        wp_schedule_single_event(time() + 120, 'my_cron_test');
    }
});
add_action('my_cron_test', function () {
    file_put_contents(WP_CONTENT_DIR . '/cron-fired-at.txt', date('c'));
});

Two minutes later check wp-content/cron-fired-at.txt. If it exists and the timestamp is close to two minutes after you saved the file, you're done. If it doesn't exist five minutes later, step 2 isn't wired up.

Five footguns the snippet fans never mention

In the order I've stepped on them:

1. The system cron isn't actually set up

The single most common failure mode. The team adds the wp-config.php line, forgets the crontab step, ships, and silently breaks everything for two weeks until the first symptom surfaces. The "fix" is to set up the system cron, but by then you've lost a backup window, missed a scheduled launch, or had Action Scheduler accumulate eight thousand pending entries.

The mitigation: before you commit the DISABLE_WP_CRON line, set up the system cron. Then run the WP Crontrol test above. Then commit both changes together, never the wp-config line alone.

2. Action Scheduler needs its own attention

Action Scheduler runs on top of wp-cron — when wp-cron fires, Action Scheduler's runner dispatches queued actions. On busy stores, a single wp-cron tick can leave actions queued because Action Scheduler caps how many it runs per dispatch (the default is 25 actions per minute).

If your external trigger fires every five minutes, you get 25 actions every five minutes maximum. On a WooCommerce store with subscriptions, abandoned-cart emails, and three integrations writing to it, you can generate more than 25 actions per minute during normal operation. The queue grows even though everything appears to be "running."

The fix: increase the per-dispatch cap (action_scheduler_queue_runner_concurrent_batches filter) or run the external trigger more often. On large stores I run it every minute and bump the concurrent batches to 4–6.

3. The cron user's PATH doesn't include PHP/WP-CLI

If you used the WP-CLI variant in step 2, the cron job runs in a stripped-down shell environment. Your interactive shell has wp on the PATH because of your .bashrc or .zshrc; the cron shell doesn't source those files. The cron line runs, can't find wp, fails silently.

The mitigation: use absolute paths in cron lines. /usr/local/bin/wp or wherever WP-CLI lives on your host. Or symlink it into /usr/bin/. The curl-based variant doesn't have this problem.

4. Timezone drift between the host cron and WordPress

The host's crontab runs in the host's timezone (often UTC). WordPress's scheduled posts are stored in the site's configured timezone. If your editor schedules a post for "09:00 Europe/Istanbul" and your server runs UTC, the post is stored as a UTC timestamp internally — and the cron mechanism handles it correctly as long as the external trigger fires often enough to catch the scheduled minute.

Where this bites is testing. You schedule a post for "two minutes from now" in WordPress, the WordPress UI shows local time, but you look at the host crontab in UTC and the math doesn't match. Three of my clients have lost time debugging this. The fix is just to schedule far enough in the future that the trigger interval doesn't matter (next-hour, not next-minute), or to test from a server in the same timezone as the site.

5. Long-running tasks getting killed mid-execution

If your scheduled work includes anything that takes a while — exporting a thousand-row CSV, processing image uploads in bulk, regenerating a sitemap on a site with 50k posts — your external trigger may have a shorter timeout than the task needs to finish. Cron-job.org's free tier has a 30-second timeout. cURL's default is much higher but server-side PHP has its own max_execution_time.

The symptom: the task runs but never completes. Action Scheduler retries it, hits the same timeout, retries again, eventually marks it failed. Your sitemap is half-rebuilt, your export is half-written.

The fix: for long-running tasks, switch the trigger to WP-CLI (wp cron event run), which doesn't have an HTTP timeout. Or break the task into smaller chunks that fit inside the timeout window. Action Scheduler's batching helps here on WooCommerce stores; for arbitrary scheduled work you may need to refactor the task.

A working measurement

A small WooCommerce store I work with: roughly 800 daily visitors, 28 active plugins, Subscriptions and an abandoned-cart plugin layered on top, all on a 4 GB VPS. Pre-change wp-cron behaviour:

  • TTFB on the homepage (cold cache): 980–1,440 ms, with high variance
  • Visible CPU spikes on the host every few minutes, lined up with the in-process wp-cron firings
  • A wp-cron.php hit visible in the access log on roughly one out of every six visitor requests

After adding DISABLE_WP_CRON and a system cron firing every minute:

  • TTFB: 740–820 ms, much tighter variance
  • CPU graph: flat baseline with one small spike per minute
  • No more wp-cron entries in the visitor access log

The TTFB improvement isn't enormous on a single page — 200–400 ms median. The variance reduction is bigger than the median move, and it's what real users actually feel. The same site without the system cron in place (the broken half-fix) had the same TTFB win but lost about three days of scheduled emails and a backup window before we caught it. The cost of the broken fix was bigger than the gain.

For more context on why in-process work like wp-cron hurts TTFB beyond just its own runtime, the survey of cache-free performance methods covers the broader pattern this fits into.

The honest scaling problem

For one site, the SSH crontab approach is fine. Two days of setup including the WP Crontrol verification and you're done.

For ten sites across different hosts — some with SSH, some on managed WP, one on a budget host that needs an external cron service — you're now maintaining ten different cron setups. Half of them have different syntax. Each one needs its own verification when something changes (a plugin update, a host migration, a server reboot that doesn't restore your crontab). The maintenance load isn't huge but it's per-site and it doesn't go away.

For sites that ride two cron systems simultaneously — Action Scheduler on WooCommerce, a custom scheduler from a membership plugin, the regular WP cron — the verification surface multiplies. You need to confirm not just that wp-cron is firing, but that every downstream scheduler is being dispatched at the rates it needs.

Where AcceleratorWP fits

AcceleratorWP's Cron Decoupler module handles the visitor-side disconnect — wp-cron stops firing on uncached page loads, the TTFB win lands. It does the verification piece automatically: it monitors whether any external trigger is actually hitting wp-cron, and if no external trigger has been seen for a configurable threshold (default: 15 minutes), it shows a sticky admin notice with the exact crontab line for your host, the Cron-job.org sign-up URL if you have no SSH, and an "I'm aware, dismiss" button that times out after 24 hours so you can't forget.

On WooCommerce stores it also watches Action Scheduler's queue depth. If pending actions accumulate past a threshold (default: 100), it surfaces a warning with the action types stacking up, so you can tune the external trigger frequency or the per-dispatch cap before the queue causes visible site issues. The shadow soak window runs the cron decoupler in observation mode for 24 hours before the visitor-side disconnect activates, so you see exactly what would have happened to your cron schedule before flipping the switch.

The numbers above — TTFB 980→780 ms with no scheduled-post regression — are from a site running Cron Decoupler with shadow soak. The visitor-side trigger was off within five minutes of the wizard finishing; the safety checks have been running quietly since and have caught one regression where a hosting provider rebooted a server and didn't restore the system crontab.

If you'd rather build it yourself, the per-page plugin loading post covers the broader pattern of how visitor-side plugin behaviour affects TTFB, and the WooCommerce checkout fix walks through the Action Scheduler footgun this post brushes against. Both are companion reads.

Decision tree

  • Single site, SSH access, you're going to verify it twice. Write the wp-config.php line, write the crontab line, run the WP Crontrol test, commit both. Total time: an hour, less if you've done it before. Maintenance: re-verify after every host migration.

  • Single site, managed host (Kinsta / WP Engine / Pressable / Cloudways). Check your host's docs. Most of them handle the system cron automatically when you set DISABLE_WP_CRON. The only manual step is verification.

  • Single site, no SSH, no managed cron panel. Sign up for cron-job.org, point it at your wp-cron URL on a 5-minute interval, verify with WP Crontrol. Free tier is enough for most sites. Add a monitor on the external service so you find out if it goes down.

  • Multiple sites, mixed hosts. Either standardise on a tooling layer that handles cron verification (AcceleratorWP's Cron Decoupler, Action Scheduler Pro, a custom uptime monitor), or accept that this is now per-site maintenance work and budget for it.

The version of this fix that ships is the one where someone is verified to be watching the cron path. The version of this fix that breaks is the one where no one checks back. The interesting decisions are about who's watching.

If you tried this and something broke in an interesting way — particularly a footgun I didn't list — I'd genuinely like to hear about it. Send notes to my inbox.

Free PDF

The 50-step WordPress performance cheatsheet.

The list I wish I'd had when I started tuning client sites — every step works without our plugin. Drop your email and the PDF lands in your inbox in under a minute. You'll also get the biweekly field notes; unsubscribe on the first click of the first email if it's not for you.

SE

Sarp Efe

Founder & developer · AcceleratorWP

Get Accelerator