WordPress Cron vs System Cron: Why Your Scheduled Tasks Drift
WordPress's built-in WP-Cron is not a real scheduler — it only fires when a visitor loads a page. System cron runs from the operating system on a fixed clock, which is why timing-critical work like backups, WooCommerce scheduled actions, and daily digests belongs there.
On this page
How WP-Cron works
Every scheduled event in WordPress (a scheduled post going live, a plugin update check, transient cleanup, WooCommerce's Action Scheduler queue) is stored in the cron option in your database as a timestamp plus a callback name. Nothing ticks in the background watching those timestamps.
Instead, page loads trigger the check. When a request comes in, WordPress asks whether any event is due. If one is, it fires a non-blocking loopback HTTP request to wp-cron.php, which runs the jobs and exits. The schedule is attached to traffic, not to time.
That design keeps WordPress portable across cheap hosts. It also means a quiet site is effectively an unscheduled site.
The plan behind Hosting Notes
Same 4 GB VPS and LiteSpeed stack we benchmark in these guides.
Where WP-Cron breaks
- Low traffic. A staging copy, a brochure site, or a shop overnight can go hours without a check. Daily jobs drift, scheduled posts publish late, and renewal reminders miss their window.
- High traffic. The inverse failure. Concurrent visitors all see the same due event and all spawn
wp-cron.php. You get duplicated work, PHP workers tied up, and the occasional runaway loop on the cron URL. - Long jobs. Backups, image regeneration, or a large import can outlast the PHP request. WordPress sets a
doing_wp_cronlock flag, but there's no hard guarantee the job finishes. - Loopback failures. If the server can't reach its own domain (basic auth in front of the site, a strict firewall, a DNS mismatch), the spawn request never lands and the job is silently dropped with no log entry.
What system cron gives you
System cron is the operating system's scheduler. You declare an entry in the crontab, and the daemon runs it whether or not a single human is browsing. It executes once, on time, under a known user, with its own output stream you can log.
The upgrade isn't ripping out WordPress's scheduler. It's driving it from a reliable clock. The event list stays in WordPress; only the trigger changes.
WP-Cron vs system cron compared
| Aspect | WP-Cron | System cron |
|---|---|---|
| Trigger | Incoming page load | Server clock |
| Low-traffic sites | Jobs missed or delayed | Runs on schedule |
| Busy sites | Duplicate spawns possible | One run per interval |
| Timing accuracy | Best effort | To the minute |
| Logging | Limited | Full, via cron output |
| Setup | Zero | One wp-config line plus a crontab entry |
How to switch WordPress to real cron
Three steps. First, stop WordPress from spawning its own runs. Add this to wp-config.php, above the "That's all, stop editing" line:
define( 'DISABLE_WP_CRON', true );
Second, add the system entry. The WP-CLI route is cleaner because it returns an exit code you can log:
*/5 * * * * cd /home/user/public_html && /usr/local/bin/wp cron event run --due-now --quiet
No WP-CLI? A plain HTTP call works:
*/5 * * * * wget -q -O - "https://example.com/wp-cron.php?doing_wp_cron=1" >/dev/null 2>&1
Third, verify. Install WP Crontrol or run wp cron event list and confirm the timestamps advance after the first window passes. If they don't, the job is failing silently. Check the cron mail or redirect stderr to a file.
Hosting choice matters here. A Hostinger VPS gives you SSH, the hPanel Cron Jobs tool, and full control over PHP binary paths. That's what you need to run WP-CLI on a real crontab instead of patching files blind. Managed WordPress plans cover the same ground if you'd rather not touch a terminal.
Before you flip the switch
- Set the interval tighter than your most frequent job. Five minutes covers most sites; heavy WooCommerce stores often run every minute.
- Use
--due-now, not--all. Forcing every event on every pass re-runs jobs that aren't ready. - Confirm the cron user can read the WordPress files and that PHP is on the CLI path. A wrong path fails without noise.
- Keep server-level backups separate from any plugin scheduler. A cron job inside WordPress can't rescue a corrupted database.
Note
Setting DISABLE_WP_CRON stops WordPress from spawning runs, but wp-cron.php stays publicly reachable. Don't block it with a firewall rule until the system entry has run successfully at least twice. Otherwise you have no fallback if the CLI path breaks.
The short version: use WP-Cron for nothing you'd notice missing, and hand anything with a deadline to the system clock. It's a one-line config change and a crontab entry, and probably the cheapest reliability upgrade on a WordPress site.
Frequently asked questions
Does disabling WP-Cron break plugins?
No. Events stay in the database exactly as before; they simply wait for your system cron job to trigger the run instead of a page visit. The only risk is forgetting to add the cron entry, which leaves the schedule frozen.
How often should system cron run wp-cron?
Every five minutes is the standard default and handles most sites. Stores running WooCommerce Action Scheduler queues, membership renewals, or subscription billing often drop to every minute for tighter timing.
Can I run system cron without setting DISABLE_WP_CRON?
You can, but traffic will still spawn extra runs alongside your scheduled ones, producing duplicate execution and wasted PHP workers. Set the constant so there's exactly one trigger.
Is wp-cron.php different from wp cron event run?
Both bootstrap WordPress and execute due events. WP-CLI avoids the HTTP round trip, returns a proper exit code, and gives you readable output, which makes failures far easier to diagnose.
Ready to get started? Check out Hostinger's plans.