Properly Setting Up WordPress Cron Jobs

As it relates to scheduling events in WordPress, there’s a lot of articles that already exist on setting up a WordPress cron job, but – and for what I believe to be a number of reasons – there’s a surprising lack of clarity around the topic.

Of course, I could be wrong – maybe I’ve just been looking in all of the wrong places.

Whatever the case may be, this is something that I’ve been dealing with in a couple of projects, and I thought I’d capture my notes here if, for no other reason, than to reference in the future.

Understanding WordPress Cron Jobs

Before actually taking a look at WordPress cron jobs, it’s important to understand exactly what a cron job is.

Simply put, a cron job is a task that is set to run at a specified interval. Generally speaking, it’s available on Linux or Unix-based system (OS X included) and, thus, most web hosts.

So if you’re looking for a way to execute a certain command several times a day, a certain time of day, a certain time of the week, month, or whatever else, then this is what you use.

What Are WordPress Cron Jobs?

For anyone who has spent time working with WordPress, then you’ve no doubt noticed the wp-cron.php file or the WordPress codex articles for wp_cron or wp_schedule_event.

wp_cron

As convenient as this functionality is, the best way that I know how to describe WordPress cron jobs are as faux cron jobs.

Here’s why: whenever a cron job is defined within the context of an operating system, it is scheduled to run by the operating system for that particular time regardless of if the person is at the computer or not.

After all, what would be the purpose of scheduling a job if it had to run manually?

When it comes to WordPress, you can schedule an event to happen at a certain interval, but it doesn’t operate like a classical cron job.

Instead, the event is set and scheduled and written to the database. The next time a user hits the site, the WordPress cron system will look to see if an event is scheduled and, if so, will then fire the event.

Notice the problem?

Someone has to visit the site before the event actually kicks off. So if you’ve scheduled something to happen hourly, but no one has visited your site in the last hour, then the event will never kick off.

If you have a very active site, then this isn’t a problem, but if you’re, say, building a plugin that needs to execute some code every hour, day, week, month, or whatever then using the default WordPress cron job system is simply not a good idea.

Setting Up a WordPress Cron Job

To give a real world example, here’s a scenario in which I’ve been working:

  • A user drops a CSV file into a directory
  • Every hour, the directory needs to be checked for the presence of a file
  • If a file exists, then the data needs to be imported and the file deleted; otherwise, the job can be skipped

Easy enough, right?

But here’s the challenge: If I setup a job using wp_schedule_event and no one visits the site, then nothing will actually happen until the next person visits the site.

Sad story.

Here’s what needs to happen:

  • From a code level, an algorithm for the above procedure needs to be defined
  • An actual Unix-level cron job needs to be scheduled to fire each hour

Specifically, the cron job defined at the Unix level needs to be setup such that it runs at whatever interval you like and that it mimics a user visiting the site.

Luckily, this is really easy to do.

Defining a WordPress Cron Job

To fully define a WordPress cron job, then this is what needs to happen:

  1. Disable the WP Cron System
  2. Setup your event code using wp_schedule_event in your plugin or in functions.php
  3. Define a cron job

Nothing terribly complicated – here’s code for how to handle all of the above.

1. Disable WP Cron

In your wp-config.php file, add the following line:

define('DISABLE_WP_CRON', true);

Done and done.

2. Setup Your Event Code

Note that YMMV based on whether or not you’re working with a plugin or with wp-config.php, but here’s the gist of everything that needs to happen.

1. Add Event Action

First, define a custom action for your, say, hourly event and give it the name of the function that will be fired each hour.

add_action( 'my_hourly_event',  'update_db_hourly' );

In our case, that function is update_db_hourly.

2. Define The Function

Now, define the function update_db_hourly for scheduling the event with WordPress.

If you’re working with a plugin, then you can schedule the event during the plugin’s activation (but also remember to remove the schedule when the plugin is deactivated):

public static function activate() {
	wp_schedule_event( time(), 'hourly', 'my_hourly_event' );
} // end activate

public static function deactivate() {
	wp_clear_scheduled_hook('my_hourly_event');
} // end activate

Finally, define the function that’s responsible for actually, y’know, doing the work:

public function update_db_hourly() {

	// 1. Check for a new file
	// 2. If it exists, read it, upload it, delete it
	// 3. Otherwise, do nothing

} // end update_csv_hourly

3. Setup The Cron Job

Most web hosts provide an interface for defining cron jobs, but not all applications are running within the context of a cPanel-based web host.

Some are simply running on an internal server or something similar where all you have is access to the command line. And considering that the cPanel is nothing more than a GUI to the command line, here’s what you need to know to define a cron job at the operating system level:

Launch the cron job file editor in your terminal using crontab -e:

crontab -e

This will bring up a seemingly empty file (if you’ve not defined any other jobs):

Empty Cron File

Next, add the following line into the cron file:

/15 * * * wget -q -O – http://yourdomain.com/wp-cron.php?doing_wp_cron

Of course, you’ll want to change up the domain for your own purposes.

The complete guide the cron commands and tools such as wget is outside the scope of this article, but this will trigger an event to fire every 15 minutes that will make a request to your WordPress website thus kicking off any scheduled tasks.

Not bad, right?

WordPress Cron Jobs

Overall, it’s not a terribly complicated procedure to setup but there are several gotchas that, unless you’re familiar with how the WordPress cron system works, can really cause some serious problems in your work.

Hopefully the notes above demystify a bit of how WordPress handles scheduled events and how you can force them to kick off using your hosting environment.

51 Comments

Just had to run manual cron jobs on a few client sites and include cron locking with `define(‘WP_CRON_LOCK_TIMEOUT’,60);` because the client was getting huge process pile ons which was having huge effect on their bill.

Do you always schedule cron manually or just leave it for 99% of the work and run manually with a proper cron job if the specific client requires it?

    This is actually a good tip and isn’t something that I’ve run into yet, so I’m glad to know what you’ve done.

    Honestly, I tend to schedule cron jobs manually along with using `wp_schedule_event` simply because I like the peace of mind that comes knowing that it’s going to run and isn’t going to rely on a page load (especially for lesser visited sites), you know?

Nice post Tom!

As the old guy, I tend to forget that even the word “cron” can be abstract to some people trying to understand. So now I usually mention that the word Cron is derived from Chronos. So in teaching, I call them Chronological jobs. Jobs that are on a calendar, that the OS will execute at said time & date.

One question I was hoping to see answered, although not a WordPress specific question, is how often is too often for a cron job?

Last year I made a mobile social photo sharing site using buddypress. Because of the lack of ios support for web apps accessing the photo library, I set it up so that people at events could upload to Flickr, and then have them pulled into buddypress. As people want to see photos as soon as possible I wanted the cron set for the shortest time possible to check for photos with the appropriate tag. I couldn’t find anything on line to say if one minute would cause too much server load. It didn’t appear to on the tests I ran. Do you or anyone know the problems of setting really short time frames for cron jobs?

    Honestly, the most frequent cron job I’ve ever run is one every 15 minutes.

    The thing is, it also depends on how much time the actual job takes to run; otherwise, they could start stacking and then – depending on your implementation – could cause some really mixed up results.

    So, in your example, let’s say one person upload 10 photos but another uploads 1,000 photos – the job would obviously take longer for the latter. In that case, you’d want to queue the jobs.

    Although “as fast as possible” would be nice, unless you’re willing to invest in some hardware or other software for splitting the jobs across multiple machines, giving a limit of photos or a wider time period of jobs would be your next best bet.

    Hope this helps!

      Thanks for your reply. The web app was only designed for a small number of people, sharing a small number of photos at an event that could be viewed by everyone, and not just their facebook or twitter friends. If anyone has any experience or links to the problems of very frequent cron jobs on low intensive tasks, I’d be interested to know.

http://www.easycron.com/generator/cronjob could help you write correct crontab syntax.

Very nice article, and gives a good understanding of WordPress cron jobs, however just one thing I m not able to understand, we have to disable the WordPress cron by defining define(‘DISABLE_WP_CRON’, true); in wp-cron.php file, and then we are also using wp_schedule_event(); function, so if we disabled wordpress cron, how is this function working? I m guessing we are not disabling wordpress cron because other default crons still working like theme and plugin updates(which are scheduled twice a day).
So what are we disabling then?

    This is a great question.

    What you’re disabling is WordPress’ built in functionality for firing its cron jobs on every page loads. This is why `wp-cron` is kind of a pseudo-solution. It works fine for low traffic sites, but when you’re getting thousands of hits per day, then you can end up suffering performance.

    Hope this helps!

Hi Tom,

We are getting an error with order emails from WooCommerce sending numerous times – like 6 or 7 times for each order confirmation email. It has only just literally started happening today (as far as I know) but is obviously very annoying for both us and customers.

Could this be an issue with WPCron do you think?

Does using the built in WPCron funtionality on each page load have a negative effect on site performance for a site that receives around 12k visits per month?

Cheers

    This all depends on how the cron job is configured.

    If you’re using WPCron – rather than true cron jobs, yes, this is absolutely a possibility. If you’re using a standard cron job, it can still happen, but it depends on what the interval is set.

    Another thing that could be happening is a flag isn’t being set properly or quick enough to denote that emails have been sent.

    I know a couple of the Woo guys and I’ll see if I can get them to chime in, as well :).

      Hi Tom,

      Yes we are using WP Cron at the minute. Looking at our log files, I think it is WP Cron which triggers the emails.

      Would changing the wp cron lockout time to something a bit longer fix the issue?

      Or do you think I should just sack off WP Cron altogether and just go to manual?

      Cheers

    Hey Aaron, I don’t think this is an issue with WP-Cron since WooCommerce doesn’t use it to send order emails — they’re sent immediately after an order is received as part of the checkout process. This maybe sounds like there’s an errant plugin duplicating the hooks that send the transactional email, check out this section: https://github.com/woothemes/woocommerce/blob/v2.0.14/woocommerce.php#L524

      Hi Max,

      I see – thanks for the update. I am using a plugin called WooCommerce Linnworks Integration to integrate woocommerce with our invoicing/shipping software.

      I believe that when we set the order as processed, and then re-sync with the website that plugin is automatically marking the orders as complete on the system.

      This error is not just happening on order received emails, but also on all order update emails – order complete etc.

      The weird thing is that sometimes one email gets sent, sometimes 2, and sometimes up to 7 or 8. Surely if there was just a duplication of transactional hooks it would just send 2 copies each time – not 7 or 8?

      On a side note, would disabling WP Cron and changing to manually configured cron jobs provide performance benefits to a site with 35k page views a month?

      Thanks

Can you reliably replicate it sending multiple orders each time you checkout? If so, I’d try disabling the Linnworks integration (though I just glanced at the code and it doesn’t look like anything would cause this) and check out again. WooCommerce by itself will not send multiple emails like this, so it’s almost certainly a plugin or theme issue that’s wreaking havoc :)

At 35k PV/month I don’t think WP-Cron would affect the performance of the site. Certainly using a system cron is way more reliable and if you have the ability to set one up, I’d definitely do that, but if you’re having performance issues they’re almost certainly with something else.

    Hi Max,

    No it doesn’t happen during checkout as far as I can tell (it hasn’t sent duplicate emails, to me at least, for the “new customer order” emails).

    This only seems to occur with order update emails – for example order completed (when you change status from processing to completed) and emails such as those when a paypal payment is reversed and it sends you a notification.

    It also happens on emails from the WooCommerce Follow Up Emails plugin available from the WooThemes website.

    One thing I noticed which is probably related, is that on the “order notes” section where it usually leaves a note saying the status has been changed from processing to completed, it has put the note 7 times on the orders where the email fires 7 times.

    Also, there is evidence from the Follow Up emails plugin reporting system that emails have been fired to the same person for the same order multiple times.

    Thanks for all your help so far – if nothing else I have learnt a lot over the last few days of trawling the net looking for answers :-)

    Oh and I meant to say – I can set up a system cron no problem. How often should I set them for? Once every 15 minutes or something?

I have done some more digging and testing and I think that maybe the issue is coming from the init hook being called multiple times.

I set up some code in the themes functions.php as follows:

add_action('init', 'test_hook');

function test_hook()
{

global $wpdb;
$message = date("m/d/y H:i:s");;
$wpdb->insert("pq_logs", array("message"=>$message), array("%s"));

}

When this is run, it actually adds more than one entry on each page load…and it seems to be somewhere between 1 and 8 times at random. This obviously means the init process is running more than once for some reason – and I am guessing the woocommerce order complete emails use init in some way.

The question is – why is init being loaded more than once?

Thanks

Hmm, I’d definitely try deactivating all your plugins and switching to the 2012 theme and then run that test again. Then, switch back to the theme you’re using, test it, and then turn on the plugins one by one until you find which one is misbehaving. Once you find that, it should be much easier to fix the problem :)

As for the cron, it depends on the sort of cron events you normally have scheduled. If you were running the Subscriptions plugin, I’d say every 2 minutes or so, but for regular cron stuff, 15 minutes is probably fine.

    Hi again,

    Thanks for your continued help!

    So should the init hook only ever be called once? Even if going into woocommerce account? Into and out of https and in the back end of wordpress also?

    Cheers

Yes, the init hook should only be called once per page load. However, this also includes AJAX requests, so when in wp-admin it can *appear* to be called multiple times because of the heartbeat script. I took a quick glance at the Linnworks code again and it’s hooking into init so I’d submit a support ticket at http://support.woothemes.com to have the Linnworks developer look at your install to see what’s going on :)

    Would there be AJAX requests in certain areas of the front-end as well? For example when clicking the “add to cart” button (seems to run twice then)? Or when clicking on the “my account” page in the menu (seems to run 3 times then)? Or maybe even from a slider?

    My test server seems to be displaying different functionality to the live site as well which is bizarre (running the same setup)…but it is difficult to test without taking the live server offline as obviously other people may be requesting pages.

    As far as I can tell, it doesn’t seem to be affected by the Linnworks plugin, so I think maybe the firing of multiple emails is just a side effect of the init error (which seems to be caused by something else).

    The only thing (on the test server) which seems to bring it back to 1 init call per page load is deactivating WooCommerce itself…

    I am literally pulling my hair out now!

    Cheers

Hi again,

Thanks for all your help – I think I eventually have worked it out (touch wood).

It seems like it was an error with my child theme and a plugin called Developer Formatter.

Hopefully it won’t start happening again.

Cheers,
Aaron

    hi there, any reason why it messed up your woocommerce? I am experiencing it right now, order is placed multiple times. but i don’t have the said plugin installed in mine. thank you in advance.

      Hi,

      I never had orders placed multiple times. Just emails sent multiple times from the site.

      I thought I had fixed it, however it seems I have just made it less severe.

      The emails are still getting fired around 1 to 3 times. As opposed to what it used to be which was around 6 to 8 times.

      Not got a clue what it could be…!!

      Cheers

Hi, thanks for the post.

One question… what is the “doing_wp_cron”? from where did that came from?

shouldn’t that be calling “update_db_hourly” in this case?

thanks

How can you generate output from the events? On my system, output from cron jobs are sent to an email address. I use this to notify me that things got done.

    Depending on the project, I just open a file such as `log.txt`, write the output, then save the file.

    There are some PHP logging libraries available that could give you more flexibility and options, but that’s what I was going when working on this particular post.

      Sorry. I should have been more clear. What I meant was how do you generate output and put it on stdout so that cron sees it? Cron automatically sends email for jobs that have output.

All of the above was really useful however another alternative is just to set the cron to load a page of the website every minute. Ensures the WordPress cron gets fired and possibly slightly less work.

Thank you for your article. I’m a new player in the game of crons-jobs. I’m receiving 2 emails “Expiring soon and Expired” on the same day the membership expires.

I have followed your instructions above on setting up a cron job.
The last instruction given was “Next, add the following line into the cron file:”
*/15 * * * * wget -q -O – http://yourdomain.com/wp-cron.php?doing_wp_cron
should this code be inserted in wp-cron.php? also is the code inserted as is or are there brackets or semicolon needed

Thank you for all your help
Sanford

    It needs to go into the system’s “crontab” file. On hosts with cpanel, you usually have a cron job manager to help you out with this.

      Exactly – this can be entered into the actual file via terminal over SSH or use the cPanel that’s included with your host.

      Thank you very much :)
      I apologize for sending another request on the same subject. I did not read the comments you sent. Please forgive me :)

      sanford

I’m trying to stop the membership expiring soon and membership expired emails being sent on the same day at the same time. I have completed the other steps. This last one I need help.
Do I just paste this code into wp-cron.php as is. is there any formatting needed?
*/15 * * * * wget -q -O – http://yourdomain.com/wp-cron.php?doing_wp_cron
Thank you for your time and labors.

Sanford Crawford
Crawford Media

I think, WordPress must use more abstract implementation for the Cron Job. So user can use “concrete” cron that fit with their system. I like how Laravel built their Queue, that basically same concept as Cron in WP.

    Yeah – WordPress’ cron implementation is pseudo-cron. It basically kicks off whenever the first person loads the site if the specified time interval has passed.

      Isn’t it also the case that all jobs that are scheduled must be completed before the page renders? I think I read that somewhere, and if that’s true a large job could hold things up a bit.
      Even with using the system cron mechanism as documented here, you still need to be careful with your design. There is no guarantee that your system will be running when a specific cron job needs to be run. For example, I have some jobs that need to run once a month, and it’s a problem if they don’t run. Rather than just running them on the first of the month and hoping they happen, I run them every day, but I record the month/year of the last run in the database. When the job first runs, it checks to see if the current month/year is the same as the last run and only continue if not.

        Isn’t it also the case that all jobs that are scheduled must be completed before the page renders? I think I read that somewhere, and if that’s true a large job could hold things up a bit.

        Yes – this is something that you’ve gotta be really careful of setting up when scheduling WordPress cron jobs (and why I prefer to use actual cron jobs to trigger page loads).

Great tutorial, however I’m left asking the question – why involve wp-cron at all?
Sure, its presence will help those who do not have shell access or cannot set up cron jobs from the server, yes. But in this tutorial, you’re setting up a cron to hit wp-cron.php via terminal every 15 minutes which will make sure than an hourly scheduled event will run. Why not just */60 and wget a file that runs update_db_hourly() and be done with it?
I guess what I’m wondering is, is there some other intrinsic value to using wp-cron? I read that it has a kind of locking system that prevents jobs from running all at once, is this the case? Are there any other benefits?

    But in this tutorial, you’re setting up a cron to hit wp-cron.php via terminal every 15 minutes which will make sure than an hourly scheduled event will run. Why not just */60 and wget a file that runs update_db_hourly() and be done with it?

    You’re right – no disagreement there. It was just meant to be an example. You can easily knock it up to 60 minutes.

    I guess what I’m wondering is, is there some other intrinsic value to using wp-cron? I read that it has a kind of locking system that prevents jobs from running all at once, is this the case? Are there any other benefits?

    Personally, I don’t use wp-cron that much. The only time I use it if it’s a site that has relatively frequent visits and only has small jobs to complete; otherwise, I stick with normal cron jobs.

Trackbacks and Pingbacks

[…] More Info […]

[…] Properly Setting Up #WordPress #Cron Jobs by @tommcfarlin tommcfarlin.com/wordpress-cron… […]

[…] year, I shared how to properly setup a WordPress cron job in which I walked through the process of defining a cron job in the operating system so that a job […]

Leave a Reply

Name and email address are required. Your email address will not be published.

You may use these HTML tags and attributes:

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>