November 15, 2017
by Matt Raines

Switching to WordPress multisite

WordPress update

Last week I went into detail about how we’ve simplified our procedure for keeping WordPress up to date by using Composer for dependency management. Stage two in our process to clean up our many separate installations of WordPress is to create a single “network” where all the sites on our server share the same core code and plugins. Why update 19 installations when you can just update one?

Before you can get started with this you need a functioning, single site installation of WordPress with permalinks already configured, which you can convert into the master site of the new multisite network. It looks like established practice is for the master site to be a fully functioning WordPress site in its own right, but I wanted to do things a little differently by hosting the network at a subdomain of my main domain and then creating the new sites as subdomains of that domain. That way, we can point our clients at (something similar to) their-website-name.websites.praterraines.co.uk so they can make changes and sign off before go live. We won’t be allowing users to create their own blogs, so there’s no real value to us in websites.praterraines.co.uk being a working site beyond giving us access to the network admin panel.

Before we can run the multisite creation tool in the admin area we need to edit wp-config.php to allow the installation.

define('WP_DEBUG', false);

/* Multisite */
define('WP_ALLOW_MULTISITE', true);

You can then return to the admin panel and use the tool to create a subdomain based multisite installation which will provide a new .htaccess and some more lines for the wp-config.php. I’ve also added some cookie related config because in my experience there are sometimes issues with logins if you don’t.

/* Multisite */
define('WP_ALLOW_MULTISITE', true);
define('MULTISITE', true);
define('SUBDOMAIN_INSTALL', true);
define('ADMIN_COOKIE_PATH', '/');
define('COOKIE_DOMAIN', '');
define('COOKIEPATH', '');
define('SITECOOKIEPATH', '');
define('DOMAIN_CURRENT_SITE', 'websites.praterraines.co.uk');
define('PATH_CURRENT_SITE', '/');
define('SITE_ID_CURRENT_SITE', 1);
define('BLOG_ID_CURRENT_SITE', 1);

We’ve got a sunrise.php which is executed on every request, which redirects any request for the master site frontend to the admin area, sets some site-specific config options on particular sites, and (by hooking in to the ms_site_not_found action) outputs a branded placeholder content with a 404 status if a user requests a site which can’t be found. This last trick allows us to point future domain names at the WordPress virtualhost before we put them live. Customers will see a placeholder until we’re ready, and then we can put the site live at any time simply by changing the URL in the WordPress network admin area.

define('BLOG_ID_CURRENT_SITE', 1);

// Include our sunrise.php which outputs a site not found page for deactivated or not present hosts.
define('SUNRISE', true);

// Just in case the passthrough doesn't work.
define('NOBLOGREDIRECT', 'https://www.praterraines.co.uk/site-placeholder/');

Here’s a snippet from our wp-content/sunrise.php:

function praterraines_hosting_site_not_found()
{
    status_header(404);
    // Output the branded placeholder.
    // ...
    exit;
}

/**
 * If the site cannot be found, output our branded placeholder content and exit.
 */
add_action("ms_site_not_found", "praterraines_hosting_site_not_found");

/**
 * The main network site doesn't display any content. It simply redirects to the admin page.
 */
add_filter("pre_get_site_by_path", function($site, $domain, $path, $segments, $paths){
    if ($domain == DOMAIN_CURRENT_SITE) {
        if (!preg_match("[/wp-(admin|login)]", $_SERVER["REQUEST_URI"])) {
            header("Location: /wordpress/wp-admin/", true, 301);
            exit;
        }
    } else {
        // ...
    }
}, 10, 5);

We do the same thing with deleted blogs by calling praterraines_hosting_site_not_found from a wp-content/blog-deleted.php as well.

Some of the plugins we use on every site allow configuration in wp-config.php to save you having to enter your mailserver details or Akismet anti-spam key on every new website. You’ll need to replace the placeholders with your specific values.

// SMTP settings
define('WPMS_ON', true);
define('WPMS_MAIL_FROM', 'info@praterraines.co.uk');
define('WPMS_MAIL_FROM_NAME', 'Prater Raines');
define('WPMS_MAILER', 'smtp'); // Possible values 'smtp', 'mail', or 'sendmail'
define('WPMS_SET_RETURN_PATH', 'false'); // Sets $phpmailer->Sender if true
define('WPMS_SMTP_HOST', $smtp_host); // The SMTP mail host
define('WPMS_SMTP_PORT', $smtp_port); // The SMTP server port number
define('WPMS_SSL', ''); // Possible values '', 'ssl', 'tls' - note TLS is not STARTTLS
define('WPMS_SMTP_AUTH', true); // True turns on SMTP authentication, false turns it off
define('WPMS_SMTP_USER', $smtp_username); // SMTP authentication username, only used if WPMS_SMTP_AUTH is true
define('WPMS_SMTP_PASS', $smtp_password); // SMTP authentication password, only used if WPMS_SMTP_AUTH is true

// Akismet (WordPress.com) API key
define('WPCOM_API_KEY', $wordpress_api_key);

Now it’s just a question of adding all the existing sites to the new multisite installation and porting the content across. More on that later.