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.
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
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.
Fire up your database client (MySQL Workbench, phpMyAdmin,
wp db cli) and let’s find out which themes plugins are active.
SELECT * FROM wp_41_options WHERE option_name = 'active_plugins';
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.
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.
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.
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.
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.
Finally, copy the original
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?
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.
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
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
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!