Getting WordPress cron work in multisite environment

There are several bits and pieces around that tell you how to get the WordPress cron work properly in simple situations. I just spent a morning getting it all working in a multisite environment so here come my notes on how to get it to work properly.

“Cron” is the scheduler of background tasks. You want to disable the built-in cron simulator when your site has the capability to run the normal UNIX cron jobs. The built-in simulator is quite unreliable and causes problems sooner or later. It is a good thing to have when the normal UNIX cron is not there but if you can install cron jobs you are better off with a standard reliable regular execution of your background tasks.

UNIX crontab format

First of all, the usually advised first step is quite correct whatever the environment. You go to the installation directory, edit your wp-config.php and add the line before that last require_once statement (or anywhere in the middle of the file really) to disable the built-in cron simulator:

/* Disable wp-cron, we run linux cron */
define('DISABLE_WP_CRON', true);

Next, for a site that is not a network, i.e. not a multisite, it is sufficient to add something like this as a cron job:

*/2 * * * * www-data /usr/bin/php /var/www/yoursite/wp-cron.php > /dev/null )

This executes the cron of WordPress every two minutes. If you cannot run the command-line PHP for some reason but have wget (or any other thing, like lynx, curl, whatever) you can add a line to cron to call a URL instead, although that’s going to consume a tiny bit more resources:

*/2 * * * * www-data /usr/bin/wget -q -O - http://yoursite/wp-cron.php > /dev/null

Many recommend adding the parameter “doing_wp_cron” at the end of the URL but that is a mistake. Having that parameter assumes that you already acquired a lock for the cron job execution and may cause problems if you have long running jobs. Without that parameter, the wp-cron will do what it is supposed to do: acquire a lock so there is no contention, and then run its jobs.

For the multisite, however, it does not work so easily. You must execute the wp-cron for each and every of the “sites” in your network separately. This is kinda nuts but that’s how things are. There is no way to do that from a command line (as far as I can tell), so you have to call the URLs at each site.

I have the following script that reads the names of the sites and goes through them one by one:

<?php
if( php_sapi_name() !== 'cli' ) {
 die("Meant to be run from command line.\n");
}

// Modify this based on site domain
$_SERVER['HTTP_HOST'] = 'yoursite.com';

define( 'WP_USE_THEMES', false );
global $wp, $wp_query, $wp_the_query, $wp_rewrite, $wp_did_header;

require_once( dirname( __FILE__ ) . '/wp-load.php' );

if ( ! function_exists( 'wp' ) )
 die( 'Couldn\'t load WordPress :(' );
if ( ! is_multisite() )
 die( 'Multisite is not enabled.' );


global $wpdb;
$sql = $wpdb->prepare("SELECT domain, path FROM $wpdb->blogs WHERE archived='0' AND deleted ='0' LIMIT 0,300", '');

$blogs = $wpdb->get_results($sql);

foreach($blogs as $blog) {
 $command = "http://" . $blog->domain . ($blog->path ? $blog->path : '/') . 'wp-cron.php';
 //echo $command . "\n";
 wp_remote_get( $command );
}
?>

The script uses the WordPress itself to connect and request the wp-cron pages at each site. Note that you must set the $_SERVER[‘HTTP_HOST’] variable at the beginning to the correct name of your website server, otherwise it will not work.

Now, if you call that script wp-cron-mu.php, you can call it from cron:

*/2 * * * * www-data /usr/bin/php /var/www/yoursite/wp-cron-mu.php > /dev/null

This will then call the script every 2 minutes and the script will, in turn, go through all websites in the WordPress database and execute the cron for them one-by-one.

7 thoughts on “Getting WordPress cron work in multisite environment

  1. I like your script but on a huge multisite firing all the sites’ crons at once will cause more problems and even down the server for a while. It’s better to run the crons in a row not alll of them at once.

  2. What stops you from inserting a sleep delay into the foreach loop that calls the URL for each site?

    (wp cron is asynchronous, so you really cannot run it sequentially, you don’t know when it will execute, but you can add a second or two delay and that will take care of spreading the load quite far apart)

  3. Great post.
    I use free cron job easycron.com which manages multiple sites and provides feedback and reporting.

  4. Thank you for your reply.
    Does the LIMIT 0,300 part mean that only 300 sites will be selected? what about a multisite with more than 300 sites?

  5. If you have more than 300 sites, I assume you will know how to program both scripts and PHP and will have your own system for executing cron jobs for WordPress. Or a system administrator who will do it for you otherwise.

  6. Is it OK to add this lines after “// Modify this based on site domain” , because I have a lot uf notices in my debug.log for undentified indexes.

    $_SERVER[“REMOTE_ADDR”] = array_key_exists( ‘REMOTE_ADDR’, $_SERVER) ? $_SERVER[‘REMOTE_ADDR’] : ‘127.0.0.1’;
    $_SERVER[“REMOTE_HOST”] = array_key_exists( ‘REMOTE_HOST’, $_SERVER) ? $_SERVER[‘REMOTE_HOST’] : gethostbyaddr($_SERVER[“REMOTE_ADDR”]);
    $_SERVER[“SERVER_PROTOCOL”] = array_key_exists( ‘SERVER_PROTOCOL’, $_SERVER) ? $_SERVER[‘SERVER_PROTOCOL’] : “HTTP/1.1”;
    $_SERVER[“REQUEST_METHOD”] = array_key_exists( ‘REQUEST_METHOD’, $_SERVER) ? $_SERVER[‘REQUEST_METHOD’] : “GET”;
    $_SERVER[“SERVER_PORT”] = array_key_exists( ‘SERVER_PORT’, $_SERVER) ? $_SERVER[‘SERVER_PORT’] : “80”;
    $_SERVER[“SERVER_SOFTWARE”] = array_key_exists( ‘SERVER_SOFTWARE’, $_SERVER) ? $_SERVER[‘SERVER_SOFTWARE’] : “Apache”;
    $_SERVER[“HTTP_ACCEPT”] = array_key_exists( ‘HTTP_ACCEPT’, $_SERVER) ? $_SERVER[‘HTTP_ACCEPT’] : “text/html,application/xhtml+xml,application/xml,application/json”;
    $_SERVER[“HTTP_HOST”] = array_key_exists( ‘HTTP_HOST’, $_SERVER) ? $_SERVER[‘HTTP_HOST’] : “www.site.com”;
    $_SERVER[“HTTP_USER_AGENT”] = array_key_exists( ‘HTTP_USER_AGENT’, $_SERVER) ? $_SERVER[‘HTTP_USER_AGENT’] : ‘Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.116 Safari/537.36’;

  7. So you are fully setting up the request like if it came from a browser? I guess that is fine and I would even say it is a good idea. My debug is switched off, so I do not see those notices at the setup of the session in WP.

Leave a Reply

Your email address will not be published. Required fields are marked *