Multi-Tenant WordPress

This post was inspired by Jason McCreary's WordPress Multitenancy (and his follow-up).

Overview

The multi-tenant WordPress setup allows multiple "tenant" sites to run from one "core" WordPress install, but with separate databases. The WordPress files are placed in versioned directories outside of the site’s root, such as /opt/wordpress/{version}/, then symlinked to the tenant sites’ root directories.

The main wp-config.php file is in /opt/wordpress and contains all shared WordPress settings and definitions, along with a bit of code to require the tenants’ config files, based on the host.

wordpress
├── 4.1/
│   ├── {... core WordPress 4.1 files ...}
├── tenant-configs
│   ├── mysite.dev-config.php
│   ├── clientsite.dev-config.php
└── wp-config.php
/opt/wordpress/
├── .htaccess
├── index.php
├── wordpress -> /opt/wordpress/4.1/
└── wp-content
The site’s root

By taking advantage of some fully supported alternative configuration and setup options, we can allow for:

  • Slow, tested rollouts of WordPress core updates
  • A dev > stage > production Git workflow in a single WordPress install
  • Separate databases for each of our sites (or branches)
  • Easily adding more sites as needed

That last point is especially attractive if you develop multiple WordPress sites; make this your local setup and you’re golden.

Getting Started

We’re essentially going to be giving WordPress its own directory, then moving the wp-content directory to the site’s root.

Give WordPress its own directory

As mentioned, the core WordPress files should be moved into versioned directories outside of the site’s root. We then symlink the version directory to the site’s root.

ln -s /opt/wordpress/4.1/ /path/to/site/wordpress
Symlink the core WordPress files to the site’s root

Copy index.php from /opt/wordpress/4.1/ to the site’s root directory and edit the last line to add the wordpress symlinked directory.

<?php /** Loads the WordPress Environment and Template */
require( dirname( __FILE__ ) . '/wordpress/wp-blog-header.php' ); ?>
Tell WordPress where to find itself

Move wp-content

In my experience, the wp-content directory isn’t necessary in the core WordPress files, so we can safely move it to our site’s root.

One tip you may find useful: Copy wp-content to the /opt/wordpress directory so you’ve got a spare you can use when adding sites in the future.

We’ll need to make some changes to the main wp-config.php and tenant config files to make sure WordPress can find our wp-content directory, but the files are now all in place.

ls -l

1 root root  418 Jan 15 22:08 index.php
1 root root   19 Jan 15 22:05 wordpress -> /opt/wordpress/4.1/
4 root root 4096 Jan 15 22:07 wp-content
We’re all set

The config files

For the main config file, the DB_NAME, DB_USER, DB_PASSWORD and DB_HOST definitions and Authentication Unique Keys should be removed and placed in the tenant config files. In their place is some code to require a tenant config file, based on the host.

<?php // From /opt/wordpress/wp-config.php

// Parse the host to create the tenant's config file path
$server_host = preg_replace('/:.*/', "", $_SERVER['HTTP_HOST']);
$server_host = preg_replace("/[^a-zA-Z0-9.\-]/", "", $server_host);
$host_config_file = '/opt/wordpress/tenant-configs/'.strtolower($server_host).'-config.php';

// Require the tenant's config file
if (file_exists($host_config_file)) {
  require_once($host_config_file);
}
?>
Require the tenant config files based on their host name

The tenant config files hold the tenant-specific database settings and Authentication Unique Keys, along with a couple declarations to tell WordPress where the wp-content directory is located.

<?php
/**
 * Required by /opt/wordpress/wp-config.php
 */

/** MySQL database name */
define('DB_NAME', 'mydatabase');

/** MySQL database username */
define('DB_USER', 'db_username');

/** MySQL database password */
define('DB_PASSWORD', 'xxxxxxxxxxxx');

/** MySQL hostname */
define('DB_HOST', 'localhost');

// Authentication Unique Keys
define('AUTH_KEY',         'randomString');
define('SECURE_AUTH_KEY',  'randomString');
define('LOGGED_IN_KEY',    'randomString');
define('NONCE_KEY',        'randomString');

// Path to the wp-content directory for this tenant
define('WP_CONTENT_DIR', '/path/to/site/wp-content');
define('WP_CONTENT_URL', 'http://mysite.com/wp-content');

?>
The last two lines tell WordPress where the wp-content directory is located.

Intall WordPress

Be sure to visit your site at mysite.com/wordpress in order to be prompted to start the installation. After installation, you shouldn’t need to include the wordpress directory in the URL when visiting the Admin Dashboard.


To add more sites:

  • Copy and modify index.php
  • Symlink the core WordPress files to the site’s root
  • Add a wp-content directory
  • Add a config file for the new tenant site
  • Visit your-site/wordpress to install

See something that’s not quite right? Have a question about something in this post? Open an issue on Github or let me know on Twitter.