Understanding WP-Cron
WP-Cron or WordPress cron is the system built into WordPress that handles the scheduling of time-based tasks. Out-of-the-box, WordPress performs a number of scheduled tasks, they include:
- WordPress core update checks
- Plugin update checks
- Theme update checks
- Publishing of scheduled posts
Plugins can also utilize WP-Cron to schedule additional tasks, such as a backup plugin scheduling recurring database backups. Therefore, it’s imperative that WP-Cron is functioning correctly.
WP-Cron takes its name from cron, which is a time-based job scheduler in Unix-like systems. However, because WordPress has very few requirements, WP-Cron is designed to work on any hosting provider (including shared hosting) without using any external software or tools for scheduling events.
For WP-Cron to schedule tasks without using external software, the cron schedule has to be checked during the lifecycle of a WordPress request. The flow looks something like this:
- During the
init
hook, WP-Cron checks the database for scheduled events - If scheduled events are due, a HTTP request is spawned to the wp-cron.php file
- wp-cron.php processes the hooks associated with the scheduled task
Although WP-Cron makes it possible to schedule events in WordPress, it’s not without its own issues.
WP-Cron is Unreliable
For WP-Cron to fire scheduled events, the WordPress site must receive regular traffic. This can be problematic for low-traffic sites and cause scheduled events to be missed if no traffic is received for a prolonged period of time.
Likewise, high-traffic sites which are heavily page cached can also run into the same scheduling issues. Page caching essentially turns WordPress into a static HTML site, which means that requests no longer execute WordPress and can no longer fire off the scheduled events.
WP-Cron is Inefficient
For high-traffic sites that can’t be page cached, WP-Cron is extremely inefficient. This is because WP-Cron will check for scheduled events on every page load. This could mean that the cron schedules are being checked multiple times per second. Most scheduled events don’t need this level of granularity, as scheduled events typically run on the minute. Therefore, checking for scheduled events on every single page load is unnecessary.
A common misconception is that every page load results in a HTTP request to wp-cron.php, but thankfully this isn’t the case. WordPress has a rudimentary locking system in place, which attempts to prevent calls to wp-cron.php from occurring more than once in a sixty-second period.
Whenever a request is spawned to wp-cron.php a lock is created (stored as a WordPress transient) which stores the current Unix timestamp. WordPress will no longer spawn additional requests to wp-cron.php until 60 seconds have passed. This system does introduce a level of atomicity, but it isn’t foolproof. High-traffic sites receiving multiple simultaneous requests can still spawn multiple requests to wp-cron.php, thus increasing server load.
Disabling WP-Cron
To disable WP-Cron, SpinupWP adds the following constant to your wp-config.php file.
define( 'DISABLE_WP_CRON', true );
This will prevent WP-Cron from automatically checking for scheduled cron events on every page load. This doesn’t disable scheduled events in WordPress completely, only the automatic checking and firing of scheduled events. To ensure that scheduled events continue to work you need an alternative method of firing the scheduled events. For multisite installs, each subsite needs its own alternative method of firing the scheduled events.
Unix Cron
In Unix-like systems, cron is a system daemon that runs in the background. Unlike WP-Cron, Unix cron runs continuously and uses the system clock to schedule tasks. This means that scheduled events are never missed.
We can use a Unix cron in conjunction with WP-CLI to reliably schedule events. Every site created in SpinupWP has an entry similar to the following added to the site user’s crontab:
0,5,10,15,20,25,30,35,40,45,50,55 * * * * cd /sites/{domain}/files/; flock -n ~/.wp_cron.lock /usr/local/bin/wp cron event run --due-now --quiet
This will run scheduled events every five minutes. The exact timing will vary by site as a random offset is used so that all crons aren’t scheduled to run at the exact same time, reducing the risk of CPU spikes.
If five minutes isn’t a good fit for the needs of any specific sites, the interval can be adjusted within SpinupWP on the Cron tab for that site. For more details, see the Managing Site Cron Jobs doc.
If you would like to add additional Unix cron jobs check out our doc on how to add new cron jobs.
Testing Cron
When using the DISABLE_WP_CRON
constant it’s important to verify that scheduled events are still working. The easiest way to do this is by using WP-CLI.
wp cron event list +----------------------+----------------------+---------------------------+------------+ | hook | next_run_gmt | next_run_relative | recurrence | +----------------------+----------------------+---------------------------+------------+ | wp_privacy_delete_ol | 2020-07-17 10:51:51 | now | 1 hour | | d_export_files | | | | | recovery_mode_clean_ | 2020-07-17 20:51:51 | now | 1 day | | expired_keys | | | | | wp_version_check | 2020-07-17 20:51:51 | now | 12 hours | | wp_update_plugins | 2020-07-17 20:51:51 | now | 12 hours | | wp_update_themes | 2020-07-17 20:51:51 | 10 hours 34 minutes | 12 hours | | wp_scheduled_delete | 2020-07-17 20:51:53 | 10 hours 34 minutes | 1 day | | delete_expired_trans | 2020-07-17 20:51:53 | 10 hours 34 minutes | 1 day | | ients | | | | | wp_scheduled_auto_dr | 2020-07-17 20:51:55 | 10 hours 34 minutes | 1 day | | aft_delete | | | | | wp_site_health_sched | 2020-07-21 20:51:51 | 4 days 10 hours | 1 week | | uled_check | | | | +----------------------+----------------------+---------------------------+------------+
If the next_run_relative
column contains several entries with a value of now
the chances are that cron events aren’t being scheduled often enough (if at all). You can roughly work out when cron events were last scheduled by comparing the next_run_gmt
column minus the recurrence
column to the current time in GMT.
If you’d prefer to verify that cron events are working via the WordPress dashboard, you can do so using the WP Crontrol plugin.