Experimental Stackable WordPress Mode

Update July 3rd 2020 – This is now being developed as a WordPress plugin. See wpfreighter.com.

WordPress can be configured as a standalone site or a multisite network. Ever since I wrote about Dynamically Load WordPress Database, I kept wondering if that method could be used to create a hybrid WordPress mode. After a weekend of exploring the idea I came up with an experimental new mode which I’m calling stackable WordPress. Here is a comparison of showing how stackable fits in between standalone and multisite.

StandaloneStackableMultisite
UploadsUnique ⭐Unique ⭐Unique ⭐
UsersUnique ⭐Unique ⭐Shared
PluginsUnique ⭐Unique ⭐Shared
ThemesUnique ⭐Unique ⭐Shared
DatabaseUnique ⭐SharedShared
Developer accessUnique ⭐SharedShared
WordPress CoreUnique ⭐SharedShared
Comparing different WordPress modes

What is Stackable WordPress?

It’s a way that you can run many WordPress websites on a single installation of WordPress without multisite. Each site is essentially stacked on top of a regular WordPress website. Now before you get too exited and think you can use this to save on your hosting services… be warned this is only an experimental mode. Also many of the limitations with multisite still apply to stackable WordPress like:

  • Too many stacked WordPress sites will require more expensive hosting services. ⚠️
  • No server resource isolation. If one site receives abnormally high loads it will adversely affect all other stacked sites. ⚠️
  • Developer access (SFTP/SSH/PHPmyadmin) is shared and will grant full access to all stacked WordPress sites. ⚠️
  • Single set of WordPress core files. Upgrading core will affect all stacked WordPress sites. ⚠️

There is also the fact that I didn’t create any GUI. Setting up stacked WordPress requires manual effort. Each regular WordPress site will need to be manually migrated over to the single stacked WordPress installation. OK so enough warnings. Let’s dig in!

Enable stackable WordPress with custom wp-config.php configurations.

In theory any WordPress site can be stackable, however I’ve only tried this out with my host provider, Kinsta. Steps to get this working with others providers will vary. Start by editing wp-config.php and copy over the following new configuration. This should override the existing line $table_prefix = "wp_";.

$table_prefix = "wp_";

// Define domain mappings for stacked sites
$stacked_mappings[1] = "austinginder.com";
$stacked_mappings[2] = "another-awesome-personal.site";

// Assign stacked ID based on requested domain
if ( isset( $_SERVER['HTTP_HOST'] ) && in_array( $_SERVER['HTTP_HOST'], $stacked_mappings ) ) {
        foreach( $stacked_mappings as $key => $stacked_mapping ) {
                if ( $stacked_mapping == $_SERVER['HTTP_HOST'] ) {
                        $stacked_id = $key;
                        continue;
                }
        }
}

// Allow manually defined stacked ID from WP_CLI
if ( defined( 'WP_CLI' ) && WP_CLI ) {
        $stacked_id = getenv( 'STACKED_ID' );
}

if ( ! empty( $stacked_id ) && ! empty ( $stacked_mappings[ $stacked_id ] ) ) {
        $table_prefix = "stacked_{$stacked_id}_";
        $stacked_home = $stacked_mappings[ $stacked_id ];
        define( 'WP_CONTENT_URL', "https://${stacked_home}/content/{$stacked_id}" );
        define( 'WP_CONTENT_DIR', dirname(__FILE__) . "/content/{$stacked_id}" );
}

Next configure domain mappings within the PHP array $stacked_mappings. This is necessary for each site you plan to add to the stacked WordPress. The first site doesn’t need included as that will be the fallback. The index number with each domain mapping is important as it will determine where the content directory is stored.

Steps to migrate a standalone site over to a stacked WordPress site

If you’ve ever moved a WordPress site into or out of a multisite network then these steps will be somewhat familiar with a little twist. Instead of special handling for just the upload folder, we’ll be pulling over a full copy of the /wp-content/ folder and renaming to /content/<stacked-site-id>/. Also instead of a partial database import, we’ll be exporting and import the entire database with renaming database prefixes. Start with a WordPress site you wish to pull into the stacked site and do the following:

  1. Export database with WP-CLI wp db export
  2. Change the table prefix to a unique table prefix starting with stacked_1_ and increment the number for each site you’re adding to the stack. This number needs to match the number in the above domain mapping. Over the command line you can do a find and replace directly do the exported SQL file like this:
    sed --in-place --expression 's#`wp_#`stacked_1_#g' database-file-2020-05-21-77387c0.sql
    sed --in-place --expression 's#'wp_user_roles#'stacked_1_user_roles#g' database-file-2020-05-21-77387c0.sql
    sed --in-place --expression 's#'wp_capabilities#'stacked_1_capabilities#g' database-file-2020-05-21-77387c0.sql
    sed --in-place --expression 's#'wp_user_level#'stacked_1_user_level#g' database-file-2020-05-21-77387c0.sql
  3. Next connect to the stacked site over SSH and import database with WP-CLI wp db import database-file-2020-05-21-77387c0.sql. If done properly, this should not overwrite any existing tables. It will simply add new stacked tables to the existing database.
  4. Update /wp-content/ references within database to new location: wp search-replace /wp-content/ /content/1/ "stacked_1_*" --all-tables --report-changed-only
  5. Copy /wp-content/ from the source site over to /content/1/ on the stacked site. The number should match the site ID chosen above.
  6. Any static content in the root level will need special handling as copying over to the root of stacked site will make it visible for all sites. Recommend coping static content from the root / to /content/1/ and configure Redirection plugin to handle the redirection. For example, to get a folder named /presentations/ redirect to /content/1/presentations/ the rule would like this.
  7. On your web server add domain mappings for the new site, point DNS to new webserver, expand SSL to cover the new stacked site and enjoy!

Supports WP-CLI ✅

Within WP-CLI you can interact with individual sites by defining STACKED_ID as an environment variable. Here is an example of how you might grab the stacked IDs from wp-config.php then loop through each site performing routine updates.

stacked_ids=$(  grep "\$stacked_mappings\[.\]" wp-config.php | awk -F '[' '{print $2}' | awk -F ']' '{print $1}' )

for stacked_id in ${stacked_ids}; do
    export STACKED_ID=$stacked_id
    wp plugin update --all
    wp theme update --all
done

If you need to target the database, arguments like --network aren’t going to work. Remember this isn’t multisite. Instead you can target by table prefixes. Here is an example of doing a find-and-replace on all tables starting with stacked_1_.

wp search-replace //olddomain.tld //newdomain.tld "stacked_1_*" --all-tables

Supports HTTPS and server caching ✅

I was surprised to find that this method sorta just works with HTTPS and server caching. No need for any custom NGINX or Apache configurations. Which can’t be said for multisite. With Kinsta there are a few things to know. You’ll need to configure “Force HTTPS” to “Requested domain”. That way each stacked site can properly stick to their own domain name.

Kinsta’s SSL management tools

Kinsta’s requests Let’s Encrypt SSLs in batch which unfortunately means there is a maximum of 10 SSL certs per site. This is due to Let’s Encrypt rate limiting. I’ve ran into this limit for other multisite networks. I hope that Kinsta improves their SSL request process. If SSLs were requested separately rather then in batch then there wouldn’t be an SSL limit.

The magic happens due to dynamic wp-config.php configuration.

With so little code you might be wondering how this is even possible. The magic happens by dynamically setting three items within wp-config.php: $table_prefix, WP_CONTENT_URL and WP_CONTENT_DIR. The dynamically assigned $table_prefix allows for a completely separate WordPress installation in the database. No more shared user accounts like multisite. The dynamically assigned WP_CONTENT_URL and WP_CONTENT_DIR allows for a unique wp-content folder per each stacked site.

So when would stacking WordPress sites be useful?

If you’re a web developer that wants to get your hands messy, then give feel free to give stacking WordPress a try. I see stacking WordPress as having a lot of potential, however it needs a GUI and some polish. As it stands right now, it should not be used in production unless you plan on supporting it. If you’re a plugin author or just someone interested building WordPress products, the feel free to take this idea of stacking WordPress and build something cool.

I see this being a good option for small, low usage WordPress sites. Even better if it’s a no usage site. As in a WordPress site that has no visitors and is used exclusively as a tool to output static content. I recently moved a few of my personal WordPress sites over to a stacked WordPress site. With the new stacked WordPress site, hosting costs are now shared with the other sites on that same instance. That works out great for my websites as I only make updates to them a few times per year.