Cron & WordPress
The WordPress Cron system is a pseudo-cron system implemented within WordPress to execute scheduled tasks and functions. It doesn't work like a traditional cron job set up on a server, but rather relies on the visits to your WordPress site to trigger scheduled events.
Here's how it generally works:
- Visitor Access: When a user accesses your WordPress site, WordPress checks if there are any scheduled tasks that need to be executed
- Task Check: WordPress compares the current time against the scheduled time of tasks stored in the database. Tasks due for execution are identified
- Task Execution: If a scheduled task's execution time has arrived, WordPress executes the associated function or action
- Completion and Recurrence: After the task is completed, WordPress reschedules it for the next occurrence based on the recurrence schedule defined for that task.
This system relies heavily on user traffic. If your site doesn’t have regular traffic, scheduled tasks might not execute on time or at all. However, there are ways to address this. For instance, you can set up external services or server-level cron jobs to trigger WordPress's wp-cron.php
file at regular intervals. This ensures that scheduled tasks are triggered even if your site doesn’t receive frequent visits.
To manually trigger the WordPress Cron system, you can visit http://yoursite.com/wp-cron.php in your browser or set up an external service to ping this URL at specified intervals.
Keep in mind that WordPress Cron can sometimes cause performance issues, especially on high-traffic sites, as it runs whenever someone visits the site. Some plugins and hosting setups offer options to disable WordPress Cron and set up a server-level cron job for better efficiency.
Internal organisation
Internally, WordPress manages its cron system through a set of functions and classes that interact to handle scheduled tasks. Here’s an overview of the internal organization:
- wp-cron.php: This file acts as the entry point for WordPress cron jobs. When a request is made to
wp-cron.php
, it checks the database for scheduled events and triggers the necessary actions - WP-Cron Functions: WordPress provides a set of functions to manage cron-related tasks:
- wp_schedule_event(): This function schedules a recurring event.
- wp_schedule_single_event(): This schedules a one-time event.
- wp_unschedule_event(): Used to unschedule an already scheduled event.
- wp_next_scheduled(): Checks when the next instance of a scheduled event will occur.
- wp_clear_scheduled_hook(): Clears all scheduled instances of a particular hook.
- Hooks and Actions: WordPress hooks like
wp_cron
andinit
are used to trigger the cron system and process scheduled events respectively. When WordPress loads, it checks for scheduled tasks and hooks necessary functions to be executed at the specified times - Cron API: WordPress includes a Cron API that manages the scheduling and execution of events. It maintains a record in table
wp_options
in the database where it stores the scheduled events with their hook names, schedules, and parameters - Execution Process: When a visitor accesses the site, WordPress checks if any cron events are due. If so, it triggers the associated actions. The execution of these tasks occurs within the request, meaning it happens within the scope of the user visit
- Server Configuration: WordPress relies on site visits to trigger its cron system. However, for high-traffic or critical tasks, it's recommended to set up a server-level cron job that triggers
wp-cron.php
at regular intervals. This ensures that scheduled tasks are processed even if the site has low traffic.
Cron list
The cron list is the list of all cron jobs. It is stored as one serialized value, in database table wp_options
with cron
as option_name
:
select * from wp_options where option_name="cron"
As all cron jobs are stored serialised in one field in wp_options
, we can retrieve it using wp option get
. That's what we did here, for a pruned version of this list, discussed below:
$ wp option get cron array ( 1701795030 => array ( 'wp_privacy_delete_old_export_files' => array ( '40cd750bba9870f18aada2478b24840a' => array ( 'schedule' => 'hourly', 'args' => array ( ), 'interval' => 3600, ), ), ), 1701834630 => array ( 'wp_update_plugins' => array ( '40cd750bba9870f18aada2478b24840a' => array ( 'schedule' => 'twicedaily', 'args' => array ( ), 'interval' => 43200, ), ), ), 1701834631 => array ( 'wp_update_themes' => array ( '40cd750bba9870f18aada2478b24840a' => array ( 'schedule' => 'twicedaily', 'args' => array ( ), 'interval' => 43200, ), ), 'wp_version_check' => array ( '40cd750bba9870f18aada2478b24840a' => array ( 'schedule' => 'twicedaily', 'args' => array ( ), 'interval' => 43200, ), ), ), 1701834636 => array ( 'wp_update_user_counts' => array ( '40cd750bba9870f18aada2478b24840a' => array ( 'schedule' => 'twicedaily', 'args' => array ( ), 'interval' => 43200, ), ), ), 1701869911 => array ( 'wp_site_health_scheduled_check' => array ( '40cd750bba9870f18aada2478b24840a' => array ( 'schedule' => 'weekly', 'args' => array ( ), 'interval' => 604800, ), ), ), 1701877829 => array ( 'recovery_mode_clean_expired_keys' => array ( '40cd750bba9870f18aada2478b24840a' => array ( 'schedule' => 'daily', 'args' => array ( ), 'interval' => 86400, ), ), ), 1701877837 => array ( 'wp_scheduled_delete' => array ( '40cd750bba9870f18aada2478b24840a' => array ( 'schedule' => 'daily', 'args' => array ( ), 'interval' => 86400, ), ), 'delete_expired_transients' => array ( '40cd750bba9870f18aada2478b24840a' => array ( 'schedule' => 'daily', 'args' => array ( ), 'interval' => 86400, ), ), ), 'version' => 2, )
Cron Schedule
The terms cron list and cron schedule are used interchangeably. However, cron schedule also has a specific meaning: the list of intervals that is used by cron - Don't ask me why they didn't call it intervals or so. E.g.:
$ wp cron schedule list +------------+-------------+----------+ | name | display | interval | +------------+-------------+----------+ | hourly | Once Hourly | 3600 | | twicedaily | Twice Daily | 43200 | | daily | Once Daily | 86400 | | weekly | Once Weekly | 604800 | +------------+-------------+----------+
Details of a cron job
Let's have a closer look at a cron job. We'll start at the the usual cron list:
$ wp cron event list +------------------------------------+---------------------+---------------------+------------+ | hook | next_run_gmt | next_run_relative | recurrence | +------------------------------------+---------------------+---------------------+------------+ | recovery_mode_clean_expired_keys | 2023-12-05 15:50:29 | now | 1 day | | wp_privacy_delete_old_export_files | 2023-12-05 15:50:30 | now | 1 hour | | wp_update_plugins | 2023-12-05 15:50:30 | now | 12 hours | | wp_update_themes | 2023-12-05 15:50:31 | now | 12 hours | | wp_version_check | 2023-12-05 15:50:31 | now | 12 hours | | wp_update_user_counts | 2023-12-05 15:50:36 | now | 12 hours | | wp_scheduled_delete | 2023-12-05 15:50:37 | now | 1 day | | delete_expired_transients | 2023-12-05 15:50:37 | now | 1 day | | wp_site_health_scheduled_check | 2023-12-06 13:38:31 | 21 hours 37 minutes | 1 week | +------------------------------------+---------------------+---------------------+------------+
"now"?
Note that in the table above, 8 out of 9 tasks have now for next_run_relative
:
When the value in next_run_relative
is "now", it means that the task is scheduled to run at the next available opportunity, which could be very soon or almost immediately. WordPress attempts to execute tasks marked as "now" as soon as the system gets a chance, usually triggered by a site visit or when the cron system checks for pending tasks.
Tasks listed with "now" in next_run_relative
are often transient, triggered on-demand, and might not stay in the scheduled list for long, as they'll execute promptly when the cron system checks and detects them as due.
As an example: Let's invoke file wp-cron.php
(through someting like https://example.com/wp-cron.php
. Now the cron list looks quite differently (and you can tell that this site is currently not visited):
$ wp cron event list +------------------------------------+---------------------+-----------------------+------------+ | hook | next_run_gmt | next_run_relative | recurrence | +------------------------------------+---------------------+-----------------------+------------+ | wp_privacy_delete_old_export_files | 2023-12-05 16:50:30 | 38 minutes 27 seconds | 1 hour | | wp_update_plugins | 2023-12-06 03:50:30 | 11 hours 38 minutes | 12 hours | | wp_update_themes | 2023-12-06 03:50:31 | 11 hours 38 minutes | 12 hours | | wp_version_check | 2023-12-06 03:50:31 | 11 hours 38 minutes | 12 hours | | wp_update_user_counts | 2023-12-06 03:50:36 | 11 hours 38 minutes | 12 hours | | wp_site_health_scheduled_check | 2023-12-06 13:38:31 | 21 hours 26 minutes | 1 week | | recovery_mode_clean_expired_keys | 2023-12-06 15:50:29 | 23 hours 38 minutes | 1 day | | wp_scheduled_delete | 2023-12-06 15:50:37 | 23 hours 38 minutes | 1 day | | delete_expired_transients | 2023-12-06 15:50:37 | 23 hours 38 minutes | 1 day | +------------------------------------+---------------------+-----------------------+------------+
Any details?
Let's delve into this first cron job, wp_privacy_delete_old_export_files
and see what's more.
Above, the field in wp_options
that contains the cron list, was explored using wp option get cron
. Let's use wp option pluck
to zoom on on this one job:
$ wp option pluck cron 1701795030 array ( 'wp_privacy_delete_old_export_files' => array ( '40cd750bba9870f18aada2478b24840a' => array ( 'schedule' => 'hourly', 'args' => array ( ), 'interval' => 3600, ), ), )
It doesn't seem to get more interesting than this. I think those hex values are just identifiers. Again: Just the name of a hook and something about timing.
Can you just delete all cron jobs?
At some sites, we seem to have some issues with an ever-growing cron list, especially with some stale jobs. Can't I just remove them all? Won't the plugins eventually fix stuff if needed? Or not, because they will never get executed again anymore?
Maybe that could be done with just one satisfying WP-CLI command, like (works, eventhough there is a small error):
wp cron event list --fields=hook --format=csv | xargs -I % wp cron event delete %