For starters, a cron is a software utility to schedule periodic script executions. Running a system backup or a security scan script are common examples of how a cron may be used. Instead of remembering to get up at 3:00am every Sunday to execute a backup script, a developer can simply automate this scheduled execution with a cron job.
Spending time in the WordPress development community, you have probably seen by now that the cool kids make use of this thing called WP Cron, or the WordPress cron system. Just like you, I wanted to be a cool kid and harness the power of this automation system. All of my favorite plugins were clearly making use of this system, after all, such as Updraft Plus regularly backing up my site and WordFence continuously scanning for vulnerabilities.
While working on high-traffic and process-heavy systems, I eventually found that the default handling of the WP Cron can be problematic, however. By default, the WordPress cron is executed on every page load—and only on page loads. This means that the WordPress cron is dependent on website traffic, can drastically slow page load times for visitors when running due jobs, and can cause harmful race conditions.
After disabling WordPress’s pseudo cron system to run on page loads and instead executing with an actual cron, you should find that scheduled processes run more reliably and that your site’s performance has improved. There’s a lot of “gotcha” moments when configuring this change, though, so I’m going to give you everything you need to get the job done smoothly! Let’s go!
Disable WP Cron on Page Loads
First, disable WP Cron from running on page loads by adding this line of code in the
wp-config.php file, before the
That is all, stop editing! comment line:
define( 'DISABLE_WP_CRON', TRUE );
This will disable the WP Cron from being ran on page loads. Now, without a scheduled system cron, it also means the WordPress cron jobs are not being ran. Doing this first helps us determine if our system cron is working or not because the new cron system will now be solely responsible for running the list of jobs.
Schedule a Cron Job
Hopefully, you found the first step to be relatively easy. Now it’s time for the “gotcha” moments that I’m going to help you avoid! It’s time to move on to the real part of this process: setting up the Linux cron job.
FYI, I’m running Ubuntu 18.04.3.
Don’t Be Root
If you are running as
root, don’t be. You should instead add a user with root access, a sudoer. We want to ensure the system is safe and does not have permission to wreak havoc on your server through your new fancy cron job. Having a responsible user account can also help pinpoint the source of problematic code.
Additionally, you will come across this warning if you are logged in as
root when using WP CLI:
Error: YIKES! It looks like you're running this as root. You probably meant to run this as the user that your WordPress installation exists under. If you REALLY mean to run this as root, we won't stop you, but just bear in mind that any code on this site will then have full control of your server, making it quite DANGEROUS.
Check Your Event List
Now that you can safely use WP CLI, it’s time to get familiar with the scheduled events on your system to know how to optimize the cron job we’ll be creating soon. Checking the event list also let’s us eventually confirm that the cron jobs are getting executed by our new system.
As your sudoer,
cd into your WordPress root folder and run the following WP CLI command:
wp cron event list
You’ll see a nice table printed out that looks like this:
+--------------------------------------+---------------------+-----------------------+---------------+ | hook | next_run_gmt | next_run_relative | recurrence | +--------------------------------------+---------------------+-----------------------+---------------+ | wp_https_detection | 2022-04-10 00:10:28 | 20 minutes 30 seconds | 12 hours | | wordfence_hourly_cron | 2022-04-10 00:26:36 | 36 minutes 38 seconds | 1 hour | | wordfence_ls_ntp_cron | 2022-04-10 00:29:49 | 39 minutes 51 seconds | 1 hour | | wordfence_daily_cron | 2022-04-10 03:26:36 | 3 hours 36 minutes | 1 day | | wp_version_check | 2022-04-10 08:09:11 | 8 hours 19 minutes | 12 hours | | wp_update_plugins | 2022-04-10 08:09:11 | 8 hours 19 minutes | 12 hours | | wp_update_themes | 2022-04-10 08:09:11 | 8 hours 19 minutes | 12 hours | | wordfence_start_scheduled_scan | 2022-04-10 17:20:00 | 17 hours 30 minutes | Non-repeating | | recovery_mode_clean_expired_keys | 2022-04-10 20:09:10 | 20 hours 19 minutes | 1 day | | delete_expired_transients | 2022-04-10 20:11:08 | 20 hours 21 minutes | 1 day | | wp_scheduled_auto_draft_delete | 2022-04-10 20:11:09 | 20 hours 21 minutes | 1 day | | wordfence_start_scheduled_scan | 2022-04-13 17:20:00 | 3 days 17 hours | Non-repeating | | wp_site_health_scheduled_check | 2022-04-14 18:01:46 | 4 days 18 hours | 1 week | +--------------------------------------+---------------------+-----------------------+---------------+
Notice that the schedule is sorted with the next scheduled runs at the top and the latest upcoming jobs at the bottom.
If you instead encountered the following error, you did not properly
cd into your WordPress root folder. The root folder for your WordPress installation contains the
wp-config.php file and the
wp-content directory. It is typically in your HTML directory such as
Error: This does not seem to be a WordPress installation.
Pass –path=`path/to/wordpress` or run `wp core download`.
Write the Job Script
Before scheduling a cron job, we need to write the script that will be scheduled for execution. Luckily, Ryan Hellyer has already done that for us. I just had to tweak it a little because of file permissions issues. Here’s my full script in the
home directory of my sudoer:
#!/bin/bash clear PATH_TO_WORDPRESS="/var/www/html" # run cron events for each site in multisite network for URL in = $(wp site list --fields=url --format=csv --path="$PATH_TO_WORDPRESS") do if [[ $URL == "http"* ]]; then wp cron event run --all --due-now --url="$URL" --path="$PATH_TO_WORDPRESS" fi done # fix permissions after running cron tasks chown -R www-data:www-data $PATH_TO_WORDPRESS
Without the last line to repair the system’s file permissions, I noticed some files were returning error codes because they were owned by my cron job sudoer. The web server user
www-data did not have access to read or execute files affected by cron jobs. In my case, it was some Elementor CSS files that were failing to be loaded.
wget -q -O - http://yourdomain.com/wp-cron.php?doing_wp_cron
Schedule the Cron Job
While still logged in as your sudoer, run the following command:
A crontab file defines a list of cron jobs owned by a user, and this command opens the current user’s crontab file for editing.
Now this is where it gets tedious and where I think many people, such as myself, get caught in figuring out very sneaky issues. I’m going to first show you my crontab and then explain what’s going on, so have a gander:
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin */5 * * * * /bin/bash /home/my-cron-user/run-wp-cron.sh
Alrighty. It looks pretty simple and benign, but let me dive into the details.
PATH Variable in Your
On the first line, notice I am setting my
PATH environment variable. The top-voted “gotcha” in this holy grail community posting on StackExchange, geirha points out that the cron uses its own set of environment variables when executing. This can cause commands to not be recognized when the cronjob runs because the system is using a different path than your user account.
To ensure the crontab jobs are ran with the same commands as your user, declare your
PATH variable again in the crontab file. You can find your user’s environment variable by
echo‘ing it like this:
Specify the Cron Job
After ensuring the path variable is consistent, I write the cronjob:
*/5 * * * * /bin/bash /home/my-cron-user/run-wp-cron.sh
Finish It Off With a Newline
As noted in the caveats section of
crontab‘s manual page 5, “cron requires that each entry in a crontab end in a newline character.” user4124 also noted this, becoming the second highest voted “gotcha” in that helpful StackExchange post, saying, “My top gotcha: If you forget to add a newline at the end of the crontab file.”
Make Sure It Works
To confirm everything is working, we’re going to observe the cron event list using WP CLI again. Still as your sudoer,
cd into your WordPress root folder and run the WP CLI command:
wp cron event list
See if there are any jobs due soon. I personally like to keep checking the event list until there is a job due “now”.
Wait until you expect your Linux cronjob to execute. Since I scheduled my cronjob to run every five minutes, I simply waited 5 minutes. If all went well, you should see that the same due “now” event has now been rescheduled.
If you wait plenty long and see many events are due “now”, you’ll know there is a problem. This happens because the events are waiting to be executed and thusly aren’t being rescheduled.
By reviewing the links I have referenced throughout this article, you can find more information about each step. Hopefully, you’ll then be able to find the answer to your situation by digging deeper. Here are the links again, for your and my convenience:
- Plugin Developer Handbook: WP-Cron on WordPress.org
- Install WP CLI
- Using WP CLI to run Cron jobs on multisite networks by Ryan Hellyer
- Properly Setting Up WordPress Cron Jobs by Tom McFarlin
- Why crontab scripts are not working? community post on StackExchange
- How To Add Jobs To cron Under Linux or UNIX by nixCraft
- crontab caveats on Linux manual page 5