Moving A Single Site Out Of A WordPress Multisite Installation

We have written how to move a single WordPress site into a Multisite installation quite a while ago. It’s time to find out how to do the opposite — migrate a site from inside a Multisite installation, into a standalone single-site WordPress install.

Let’s get splittin’ ;)

0. Before You Begin

As with any hardcore database and file fiddling, a backup should always be done. If you’re with us on Pressjitsu, visit your panel and request an on-demand offsite backup.

Ready? You’ll need to take note of the Site ID you want to move out of the Network. Visit the Network Admin → Sites section, and find out the site ID of the one you want to move out. The Site ID will be visible in the URL when you click (or hover) on the site in the list. For example site-info.php?id=43 means the site you want to migrate has the ID of 43.

Finding the Site ID in a Multisite Network
Finding the Site ID in a Multisite Network

You’ll also need to find out the table prefix for your installation. This can be retrieved from the wp-config.php file on your server, inside you will find the $table_prefix variable.

Noted?

1. wp-content

Let’s start by assembling the new wp-content folder. This will contain a copy of site active plugins, network-wide plugins, must-use plugins, the child and parent theme, the uploads. Create it somewhere outside of your WordPress installation.

Plugins

Fire up your database client (MySQL Workbench, phpMyAdmin, mysql or wp db cli) and let’s find out which themes plugins are active.

SELECT * FROM wp_41_options WHERE option_name = 'active_plugins';

Note how wp_41 is the $table_prefix and the site ID we noted down in step 0. This will be done in most of the other steps when working with the database.

Copy each plugin from the Multisite setup into the new wp-content/plugins/ you created above.

Network Plugins

A similar list of Network-activated plugins (site-wide plugins) is stored in the site meta:

SELECT * FROM wp_sitemeta WHERE meta_key = 'active_sitewide_plugins';

Note that if you’re running WordPress Multisite in “multi-network” mode (extremely rare and unlikely), you’ll have multiple entries for sitewide plugins, so make sure you grab the one that is specific to your network ID.

Done?

With a whole bunch of plugins here, automating the copying step becomes inevitable. Writing the Python, perl or PHP script is left as an exercise for the reader.

Also copy the index.php file, which disallows index retrieval.

Must-use Plugins

Let’s copy the wp-content/mu-plugins/ directory as is. There’s not much else to do here. Note that mu-plugins in a single site installation works exactly the same as in Multisite — these plugins are active with no way to deactivate them through the admin UI. The “mu” part in “mu-plugins” stands for “must-use” and is not specific to Multisite.

Now would be a great time to go through the list and prune the plugins that you will not be using.

Themes

Moving onto the themes, let’s find out the parent and child theme currently in use on the site you’re planning to move out.

SELECT * FROM wp_41_options WHERE option_name IN ('stylesheet', 'template');

Copy the two theme folders from your existing themes directory, into your new wp-content/themes/ folder. If you’re not using a child theme, both rows will contain the same theme name.

Also copy the index.php file, which disallows index retrieval.

Uploads

Finally, copy the original wp-content/uploads/sites/43/ to wp-content/uploads/ in your migration directory. Note the additional sites/ID part being used in the original structure, but not in the new one.

2. Other files

Some themes and plugins may put custom files outside of the uploads directories. Big no-no, but it happens. You’ll have to manually figure these out. Don’t forget to copy any other verification files, favicons, etc. from the webroot.

You do not need to copy the WordPress core files. You didn’t do any changes to them, right?

3. wp-config.php

The WordPress configuration file might contain some special directives required for one of the plugins to work. Take note of them. You’ll need to paste them into the target wp-config.php file in your single WordPress installation. Do not copy the Multisite settings at all.

4. The Database

SHOW TABLES LIKE 'wp_43_%';

You will need to dump all these tables, and two more — the wp_users and the wp_usermeta global tables. Here’s how you’d do this via the WP-CLI utility:

wp db export dump.sql --quick --single-transaction --default-character-set=utf8mb4 --tables=wp_users,wp_usermeta,wp_43_options,....

Great. Now you have your dump.sql file. Go ahead and open it up in your favorite text editor, find the wp_users table and rename it to wp_43_users. Do the same for wp_usermeta. Do not overwrite anything else other than the CREATE and INSERT table statements. Do not touch the insert data. This is very important. This will help us avoid changing all the prefixes, and get away with just changing the $table_prefix in the wp-config.php file to wp_43_, which you will need to do.

5. Migration

When installing the new single-site installation, change the WordPress database prefix to wp_43_. After the installation is complete, copy over the wp-content directory you’ve been assembling, overwriting everything; your extra files if any; and finally import the dump.sql file.

You might need to fix your permissions before or after, just in case:

find -type f -print0 | xargs -0 chmod 0644
find -type d -print0 | xargs -0 chmod 0755

6. Upload Rewrite

Remember how we moved all the uploads from uploads/site/X to just uploads? We’ll need to rewrite their paths. With WP-CLI this is easy. You might want to use a PHP-based Search And Replace for WordPress UI if you’re not comfortable with the command line (in which case, you should probably not be doing any of this to begin with :D)

We need to replace “wp-content/uploads/sites/43” with “wp-content/uploads”, so:

wp search-replace "wp-content/uploads/sites/43" "wp-content/uploads" --all-tables

At this point, you might have hardcoded URLs in your theme and plugin files (time to frown upon your developer team now). You can spot these easily by inspecting your web server’s 404 logs.

7. User Cleanup

Since the wp_users and wp_usermeta tables in a Multisite WordPress installation are shared among all the sites, they will contain all the users from the Network. You will need to delete these.

It’s time to do some further SQL. Delete all users which didn’t have any capabilities on our site.

DELETE FROM wp_43_users WHERE ID NOT IN (SELECT user_id FROM wp_43_usermeta WHERE meta_key = 'wp_43_capabilities');

Delete any lingering metadata:

DELETE FROM wp_43_usermeta WHERE user_id NOT IN (SELECT ID FROM wp_43_users);

8. Plugin Merge

The active plugins for the site did not include the ones active for the network, which means that they will be deactivated. We will need merge the serialized values for the two, and they’re different formats. Here’s how you do it in PHP:

$plugins = unserialize( 'value from active_plugins' );
$network_plugins = unserialize( 'value from active_sitewide_plugins' );

$all_plugins = array_merge( $plugins, array_keys( $network_plugins ) );

sort( $all_plugins );

echo serialize( $all_plugins );

Update the ‘active_plugins’ in wp_43_options value to the one output by the above script.

9. Final Thoughts

If you did everything correctly, then it should be safe to delete the site from the Multisite installation now. You may want to change all administrator passwords at this point, as to not have them shared across different installations.

Other than that, you should be good to go :) Well done!