How to Convert a WordPress Single Site to a Directory-Based Multisite

Learn how to convert a live WordPress single site into a directory-based Multisite using WP-CLI, with backups, rewrite rules, database checks, and testing.

WordPress single site converted into a directory-based Multisite network using WP-CLI

Converting a fresh WordPress install to Multisite is fairly easy. Converting an existing production site is different.

The Multisite switch itself takes only one WP-CLI command. The real work is making sure the existing site still works afterward – its URLs, plugins, database tables, rewrite rules, admin area, and any new subsites you create.

This tutorial walks through converting an existing WordPress single-site installation into a directory-based Multisite on a production server.

The final URL structure will look like this:

https://example.com/
https://example.com/site-one/
https://example.com/site-two/

It will not use subdomains such as:

https://site-one.example.com/
https://site-two.example.com/

I am using dummy server information throughout this guide. Replace the domain, username, paths, and database prefix with the values from your own server.

Example server setup used in this guide

Domainexample.com
WordPress path/home/example.com/public_html
Linux userwpuser123
Database prefixwp_
Network typeDirectory-based
Test subsitehttps://example.com/multisite-test/

1. Do not run WP-CLI as root unless you have a very good reason

If you are logged into the server as root and run:

wp core version

WP-CLI may warn that you are running WordPress as root.

You can bypass the warning with --allow-root, but I would not use that as the normal approach on a production WordPress server. Many WP-CLI commands load WordPress, which can also load plugin and theme code. Running that code as root gives it far more system access than it needs.

A better approach is to find the Linux user that owns the site and run WP-CLI as that user.

2. Find the WordPress installation

If the server hosts several websites, locate the WordPress configuration files first:

find /home -maxdepth 4 -type f -name wp-config.php -print

You may get a result such as:

/home/example.com/public_html/wp-config.php

That tells us the WordPress document root is probably:

/home/example.com/public_html

3. Find the Linux owner of the site

Check who owns wp-config.php:

stat -c 'Owner: %U | Group: %G | File: %n' \
/home/example.com/public_html/wp-config.php

For example:

Owner: wpuser123 | Group: wpuser123 | File: /home/example.com/public_html/wp-config.php

Also inspect the parent directory and document root:

ls -ld /home/example.com /home/example.com/public_html

Do not change ownership just because the groups look different. Hosting panels, LiteSpeed setups, and PHP handlers sometimes use different groups intentionally.

4. Make sure the Linux account is valid

getent passwd wpuser123

A normal result looks something like:

wpuser123:x:1013:1013:,,,:/home/example.com:/bin/bash

From this point on, run WordPress commands as that user:

sudo -u wpuser123 -H wp \
  --path=/home/example.com/public_html \
  COMMAND

The -H flag makes sudo use the target user’s home directory instead of keeping root’s home environment.

5. Confirm that you are working on the right WordPress site

Check the WordPress version:

sudo -u wpuser123 -H wp \
  --path=/home/example.com/public_html \
  core version

Then check the site’s home URL:

sudo -u wpuser123 -H wp \
  --path=/home/example.com/public_html \
  option get home

And the WordPress installation URL:

sudo -u wpuser123 -H wp \
  --path=/home/example.com/public_html \
  option get siteurl

On a normal root-domain installation, both will usually return:

https://example.com

If either command points somewhere unexpected, stop and find out why before touching Multisite.

6. Check whether Multisite is already enabled

sudo -u wpuser123 -H wp \
  --path=/home/example.com/public_html \
  core is-installed --network

echo $?

The shell exit code tells us what WordPress found:

  • 0 – Multisite is already installed.
  • 1 – the installation is currently single-site.

If you get 0, do not run the conversion command again.

7. Check free disk space

df -h /home/example.com

This is easy to overlook. A database export and filesystem archive both need free space, and filling a production filesystem can cause much bigger problems than a failed Multisite conversion.

8. Check the database prefix

sudo -u wpuser123 -H wp \
  --path=/home/example.com/public_html \
  config get table_prefix

The result might be:

wp_

A custom prefix is fine. We only need to know it because new Multisite tables will use the same prefix.

9. Record the current permalink structure

sudo -u wpuser123 -H wp \
  --path=/home/example.com/public_html \
  option get permalink_structure

A common result is:

/%postname%/

Keep a note of this. After the conversion, we will test both the existing site’s URLs and pretty permalinks inside a subsite.

10. Check active plugins and the current theme

List active plugins:

sudo -u wpuser123 -H wp \
  --path=/home/example.com/public_html \
  plugin list --status=active

Then check the active theme:

sudo -u wpuser123 -H wp \
  --path=/home/example.com/public_html \
  theme list --status=active

Caching, security, authentication, redirect, and custom plugins deserve extra attention because they may behave differently on Multisite.

If you are developing your own plugin for the network, keeping plugin code out of functions.php becomes even more useful in a Multisite setup. I covered the basic structure in Your First Real Plugin: Headers, Structure and Not Using functions.php.

11. Check for must-use plugins

ls -la /home/example.com/public_html/wp-content/mu-plugins/ \
  2>/dev/null || echo "No mu-plugins directory"

MU plugins cannot be disabled from the normal Plugins screen, so review them separately if the directory exists.

12. Check for existing Multisite constants

grep -nE \
'MULTISITE|SUBDOMAIN_INSTALL|DOMAIN_CURRENT_SITE|PATH_CURRENT_SITE|SITE_ID_CURRENT_SITE|BLOG_ID_CURRENT_SITE' \
/home/example.com/public_html/wp-config.php

On a normal single-site installation this may print nothing.

If Multisite constants are already present even though WordPress reports a single-site installation, investigate that mismatch before going further.

13. Optional – create a fresh database backup

You can skip this step if you already have a recent backup that you know can be restored. I would not skip it simply because a backup system is “probably” running somewhere.

Create a directory outside public_html:

mkdir -p /home/example.com/backups

chown wpuser123:wpuser123 /home/example.com/backups

chmod 750 /home/example.com/backups

Export the database:

sudo -u wpuser123 -H wp \
  --path=/home/example.com/public_html \
  db export \
  /home/example.com/backups/example-before-multisite.sql \
  --add-drop-table

The --add-drop-table option is useful for a full rollback because the dump can remove existing tables before recreating them.

Check that the file exists:

ls -lh /home/example.com/backups/example-before-multisite.sql

14. Check the database before converting it

sudo -u wpuser123 -H wp \
  --path=/home/example.com/public_html \
  db check

The existing WordPress tables should report OK.

This step is worth doing even when you already have a backup. WordPress is about to create network tables and change the site’s database structure. It is better to know about an existing table problem before that happens.

15. Optional – back up the WordPress files

If you already have a verified filesystem backup, you can skip this part.

Otherwise:

cd /home/example.com

tar \
  --exclude='./backups' \
  -czf backups/example-files-before-multisite.tar.gz \
  public_html

Set the archive owner:

chown wpuser123:wpuser123 \
  /home/example.com/backups/example-files-before-multisite.tar.gz

Then test that the archive can actually be read:

tar -tzf \
  /home/example.com/backups/example-files-before-multisite.tar.gz \
  | head -20

16. Optional – create backup checksums

A checksum gives you a simple way to detect accidental corruption later.

sha256sum \
  /home/example.com/backups/example-before-multisite.sql \
  > /home/example.com/backups/example-before-multisite.sql.sha256
sha256sum \
  /home/example.com/backups/example-files-before-multisite.tar.gz \
  > /home/example.com/backups/example-files-before-multisite.tar.gz.sha256

17. Optional – save separate copies of wp-config.php and .htaccess

These are the two configuration files most likely to change during the conversion, so I like having small standalone copies even when a full backup already exists.

cp -a \
  /home/example.com/public_html/wp-config.php \
  /home/example.com/backups/wp-config.php.pre-multisite
cp -a \
  /home/example.com/public_html/.htaccess \
  /home/example.com/backups/.htaccess.pre-multisite

18. Check for root-level URL conflicts

This matters much more with a directory network than it does with a subdomain network.

Suppose your current website already has these pages:

https://example.com/news/
https://example.com/support/
https://example.com/contact-us/

You should not later try to create subsites using news, support, or contact-us as their slugs.

List the existing published page slugs:

sudo -u wpuser123 -H wp \
  --path=/home/example.com/public_html \
  post list \
  --post_type=page \
  --post_status=publish \
  --fields=ID,post_name,post_title \
  --format=table

A directory-based network shares the root URL space with the main site, so slug planning matters.

19. Check the homepage configuration

sudo -u wpuser123 -H wp \
  --path=/home/example.com/public_html \
  option get show_on_front
sudo -u wpuser123 -H wp \
  --path=/home/example.com/public_html \
  option get page_on_front

You may not need these values during the conversion, but they are useful if the main site’s homepage behaves differently afterward.

20. Temporarily deactivate normal plugins

If the site can tolerate a short maintenance window, deactivate ordinary plugins before the conversion.

For one plugin:

sudo -u wpuser123 -H wp \
  --path=/home/example.com/public_html \
  plugin deactivate example-plugin

Or deactivate all normal plugins:

sudo -u wpuser123 -H wp \
  --path=/home/example.com/public_html \
  plugin deactivate --all

Check that nothing is still active:

sudo -u wpuser123 -H wp \
  --path=/home/example.com/public_html \
  plugin list --status=active

Do not delete the plugins, and do not deactivate the active theme.

21. Convert the site to directory-based Multisite

Now we can perform the actual conversion:

sudo -u wpuser123 -H wp \
  --path=/home/example.com/public_html \
  core multisite-convert \
  --title="Example Network"

Notice that the command does not contain:

--subdomains

Leaving that option out gives us the directory-style network we want:

https://example.com/site-one/
https://example.com/site-two/

When the command succeeds, WP-CLI creates the Multisite database tables and adds the network constants to wp-config.php.

Once it reports success, do not run multisite-convert again.

22. Check the Multisite constants added to wp-config.php

grep -nE \
'MULTISITE|SUBDOMAIN_INSTALL|DOMAIN_CURRENT_SITE|PATH_CURRENT_SITE|SITE_ID_CURRENT_SITE|BLOG_ID_CURRENT_SITE' \
/home/example.com/public_html/wp-config.php

You should find values similar to:

define( 'MULTISITE', true );
define( 'SUBDOMAIN_INSTALL', false );
define( 'DOMAIN_CURRENT_SITE', 'example.com' );
define( 'PATH_CURRENT_SITE', '/' );
define( 'SITE_ID_CURRENT_SITE', 1 );
define( 'BLOG_ID_CURRENT_SITE', 1 );

The line that confirms the network type is:

define( 'SUBDOMAIN_INSTALL', false );

false means the network uses directories rather than subdomains.

23. Check that WordPress recognizes the network

sudo -u wpuser123 -H wp \
  --path=/home/example.com/public_html \
  core is-installed --network

echo $?

The expected exit code is:

0

Then list the sites:

sudo -u wpuser123 -H wp \
  --path=/home/example.com/public_html \
  site list

At this point you will normally see only the original site:

https://example.com/

24. Confirm the network type from inside WordPress

sudo -u wpuser123 -H wp \
  --path=/home/example.com/public_html \
  eval '
echo "Multisite: ";
var_export( is_multisite() );
echo PHP_EOL;

echo "Subdomain install: ";
var_export( is_subdomain_install() );
echo PHP_EOL;
'

The result should be:

Multisite: true
Subdomain install: false

25. Replace the old single-site rewrite rules

The database conversion is only part of the job. A directory-based network also needs rewrite rules that understand subsite paths.

First inspect the current file:

cat /home/example.com/public_html/.htaccess

If it contains the normal WordPress single-site block, replace that WordPress section with the Multisite version:

# BEGIN WordPress Multisite
# Using subfolder network type.

RewriteEngine On
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
RewriteBase /
RewriteRule ^index\.php$ - [L]

# Add a trailing slash to /wp-admin
RewriteRule ^([_0-9a-zA-Z-]+/)?wp-admin$ $1wp-admin/ [R=301,L]

RewriteCond %{REQUEST_FILENAME} -f [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^ - [L]

RewriteRule ^([_0-9a-zA-Z-]+/)?(wp-(content|admin|includes).*) $2 [L]
RewriteRule ^([_0-9a-zA-Z-]+/)?(.*\.php)$ $2 [L]
RewriteRule . index.php [L]

# END WordPress Multisite

These rules let the shared WordPress installation handle URLs such as:

/site-one/
/site-one/wp-admin/
/site-one/sample-page/

If your .htaccess also contains hosting-panel rules, security directives, redirects, PHP configuration, or LiteSpeed settings, do not wipe out the whole file. Replace only the WordPress rewrite section unless you know exactly what the other directives do.

Nginx does not use .htaccess, so an Nginx-only setup needs equivalent server-level rewrite rules instead.

26. Check .htaccess ownership and permissions

ls -l /home/example.com/public_html/.htaccess

A normal result might look like:

-rw-r--r-- 1 wpuser123 wpuser123 ... .htaccess

If editing the file as root changed its owner, restore it:

chown wpuser123:wpuser123 \
  /home/example.com/public_html/.htaccess

Typical permissions are:

chmod 644 /home/example.com/public_html/.htaccess

27. Flush rewrite rules

sudo -u wpuser123 -H wp \
  --path=/home/example.com/public_html \
  rewrite flush

You want:

Success: Rewrite rules flushed.

You can also flush the WordPress object cache:

sudo -u wpuser123 -H wp \
  --path=/home/example.com/public_html \
  cache flush

If your SSH connection drops after WP-CLI has already printed a success message, do not rerun the Multisite conversion just because the terminal disconnected. Reconnect and check the current state first.

If something does fail later, enable logging without showing PHP errors to visitors. The setup in WP_DEBUG_LOG Without Displaying Errors to Visitors is a much safer production debugging approach than turning on visible error output.

28. Test the existing site before creating a subsite

Check the homepage:

curl -I https://example.com/

A healthy result will normally include:

HTTP/2 200

Then test Network Admin:

curl -I https://example.com/wp-admin/network/

When you use curl without an authenticated WordPress session, a 302 redirect to wp-login.php is normal.

It means the route exists and WordPress is asking you to log in.

29. Create a temporary subsite

This is the test that tells us whether the network actually works, not just whether WordPress created some database tables.

sudo -u wpuser123 -H wp \
  --path=/home/example.com/public_html \
  site create \
  --slug=multisite-test \
  --title="Multisite Test"

A successful result should look similar to:

Success: Site 2 created: https://example.com/multisite-test/

List the network again:

sudo -u wpuser123 -H wp \
  --path=/home/example.com/public_html \
  site list

You should now have something like:

1  https://example.com/
2  https://example.com/multisite-test/

30. Test the subsite frontend

curl -I https://example.com/multisite-test/

You want an HTTP 200.

If the main site works but the subsite returns 404, look at rewrite rules and web-server configuration first.

31. Test the subsite admin URL

curl -I https://example.com/multisite-test/wp-admin/

A 302 redirect to login is expected when the request is not authenticated.

The important part is that WordPress understands /multisite-test/wp-admin/ as the admin route for that subsite.

32. Check the subsite home and siteurl values

sudo -u wpuser123 -H wp \
  --path=/home/example.com/public_html \
  option get home \
  --url=https://example.com/multisite-test/

Expected:

https://example.com/multisite-test

Then:

sudo -u wpuser123 -H wp \
  --path=/home/example.com/public_html \
  option get siteurl \
  --url=https://example.com/multisite-test/

It should return the same subsite URL.

33. Check the new subsite database tables

If the new site’s ID is 2 and your prefix is wp_, run:

sudo -u wpuser123 -H wp \
  --path=/home/example.com/public_html \
  db tables --all-tables-with-prefix \
  | grep '^wp_2_'

You should see tables such as:

wp_2_commentmeta
wp_2_comments
wp_2_links
wp_2_options
wp_2_postmeta
wp_2_posts
wp_2_term_relationships
wp_2_term_taxonomy
wp_2_termmeta
wp_2_terms

The main site keeps its original tables:

wp_posts
wp_postmeta
wp_options
wp_terms

User accounts are shared through network-level tables such as:

wp_users
wp_usermeta

This is an important detail when writing Multisite plugins. Code that assumes every table belongs to a single site can behave very differently once $wpdb->prefix changes with the current blog.

34. Check the Multisite network tables

sudo -u wpuser123 -H wp \
  --path=/home/example.com/public_html \
  db tables --all-tables-with-prefix \
  | grep -E '^wp_(blogs|site|sitemeta)$'

You should find tables such as:

wp_blogs
wp_site
wp_sitemeta

35. Test a real pretty permalink inside the subsite

A working subsite homepage is good, but it does not prove that deeper routes work.

Create a temporary page:

sudo -u wpuser123 -H wp \
  --path=/home/example.com/public_html \
  post create \
  --url=https://example.com/multisite-test/ \
  --post_type=page \
  --post_title="Multisite Test Page" \
  --post_name=multisite-test-page \
  --post_status=publish

Then request the pretty permalink:

curl -I \
https://example.com/multisite-test/multisite-test-page/

If that returns HTTP 200, the full request path is working:

domain
→ subsite path
→ Multisite lookup
→ rewrite rules
→ subsite
→ page permalink

This kind of end-to-end test is much more useful than assuming the problem must be where an error first appears. That same debugging idea comes up in The Error Line Is Not Where the Bug Is.

36. Check Network Admin in the browser

Log into:

https://example.com/wp-admin/

You should now see:

My Sites
→ Network Admin

The direct Network Admin URL is:

https://example.com/wp-admin/network/

Open the Sites screen and make sure both the root site and test subsite appear.

37. Reactivate plugins one at a time

Do not immediately network-activate every plugin that was active before the conversion.

If a plugin belongs only on the main site, activate it there:

sudo -u wpuser123 -H wp \
  --path=/home/example.com/public_html \
  plugin activate example-plugin \
  --url=https://example.com/

Check its status:

sudo -u wpuser123 -H wp \
  --path=/home/example.com/public_html \
  plugin list \
  --url=https://example.com/

Then retest both sites:

curl -I https://example.com/
curl -I https://example.com/multisite-test/

Only network-activate a plugin when you actually want it running across every site:

wp plugin activate example-plugin --network

Site activation and network activation are different choices. Treat them that way.

38. Leave caching until the network is known to work

If you temporarily disabled a WordPress caching plugin, leave it off until the main site, Network Admin, subsite, and nested permalink tests all pass.

Your web server may still be LiteSpeed even when the LiteSpeed Cache WordPress plugin is inactive. Those are separate things.

If you also use a CDN, purge its cache after the Multisite routing is settled. Cached redirects or cached 404 responses can make a correct configuration look broken.

39. Optional – make a post-conversion database backup

Once everything works, a second database snapshot gives you a clean “Multisite working” restore point.

sudo -u wpuser123 -H wp \
  --path=/home/example.com/public_html \
  db export \
  /home/example.com/backups/example-after-multisite.sql \
  --add-drop-table

You then have two useful states:

example-before-multisite.sql
example-after-multisite.sql

40. Delete the temporary test subsite

First check the site IDs again:

sudo -u wpuser123 -H wp \
  --path=/home/example.com/public_html \
  site list

If the test site is really site ID 2, remove it:

sudo -u wpuser123 -H wp \
  --path=/home/example.com/public_html \
  site delete 2 --yes

Always verify the ID before using site delete.

The original production site is normally blog ID 1. Do not blindly copy a site-delete command from a tutorial and assume the numbers match your network.

41. Check whether the temporary tables are gone

sudo -u wpuser123 -H wp \
  --path=/home/example.com/public_html \
  db tables --all-tables-with-prefix \
  | grep '^wp_2_'

If WordPress removed the site cleanly, this should normally return nothing.

You can also check for a leftover uploads directory:

ls -ld \
  /home/example.com/public_html/wp-content/uploads/sites/2 \
  2>/dev/null \
  || echo "Site 2 uploads directory does not exist"

Do not start manually dropping tables simply because you see something you do not recognize. Confirm that it is truly orphaned first.

42. Run one final Multisite check

sudo -u wpuser123 -H wp \
  --path=/home/example.com/public_html \
  eval '
echo "Multisite: " .
  ( is_multisite() ? "yes" : "no" ) .
  PHP_EOL;

echo "Network type: " .
  ( is_subdomain_install() ? "subdomain" : "subdirectory" ) .
  PHP_EOL;

echo "Main site: " .
  network_home_url() .
  PHP_EOL;
'

You want:

Multisite: yes
Network type: subdirectory
Main site: https://example.com/

And finally:

sudo -u wpuser123 -H wp \
  --path=/home/example.com/public_html \
  core is-installed --network

echo $?

The expected exit code is 0.

43. Check a few real production URLs

curl -I https://example.com/
curl -I https://example.com/about-us/
curl -I https://example.com/contact-us/
curl -I https://example.com/services/

These should return HTTP 200 unless you intentionally redirect one of them.

Also check:

curl -I https://example.com/wp-admin/network/

A 302 to the login page is normal for an unauthenticated request.

44. Create real subsites when you are ready

You can now add sites from:

My Sites
→ Network Admin
→ Sites
→ Add New

Or use WP-CLI:

sudo -u wpuser123 -H wp \
  --path=/home/example.com/public_html \
  site create \
  --slug=company-a \
  --title="Company A"

That creates:

https://example.com/company-a/

A few security details worth keeping

Multisite gives the Super Admin much more control than an ordinary site administrator, so protect that account carefully. Use a strong unique password and multi-factor authentication when possible.

Keep using the site’s Linux user for WP-CLI instead of root:

sudo -u wpuser123 -H wp ...

Keep database and filesystem backups outside the public web directory. This is safer:

/home/example.com/backups/

than placing SQL dumps somewhere under:

/home/example.com/public_html/

And do not network-activate a plugin simply because Multisite gives you the button. First decide whether every site really needs it.

What changes in the database after Multisite is enabled?

WordPress does not create a completely separate database for every subsite.

The original site continues using tables such as:

wp_posts
wp_postmeta
wp_options
wp_terms

A second site gets numbered tables:

wp_2_posts
wp_2_postmeta
wp_2_options
wp_2_terms

A third site would use:

wp_3_posts
wp_3_postmeta
wp_3_options
wp_3_terms

User accounts remain shared through tables such as wp_users and wp_usermeta, while network information is kept in tables including wp_blogs, wp_site, and wp_sitemeta.

This matters when writing custom code. A plugin that stores site-specific data should understand the current blog context instead of assuming the root site’s prefix is always correct.

Do not ignore the SEO side of the conversion

Turning on Multisite does not automatically hurt SEO. Changing existing URLs without noticing can.

After the conversion, make sure important root-site URLs still resolve exactly as they did before:

https://example.com/about-us/
https://example.com/contact-us/
https://example.com/services/

Also check canonical URLs, XML sitemaps, robots directives, structured data, Open Graph URLs, pagination, media URLs, and redirects.

Most importantly, do not create a subsite whose slug already belongs to indexed content on the main site.

How do you know the conversion is really finished?

I would consider the job complete only after all of these checks pass:

  1. The original homepage returns HTTP 200.
  2. Existing pages and posts still work.
  3. is_multisite() returns true.
  4. is_subdomain_install() returns false.
  5. wp core is-installed --network exits with 0.
  6. Network Admin opens correctly.
  7. A temporary directory-based subsite can be created.
  8. The subsite homepage returns HTTP 200.
  9. The subsite admin route works.
  10. The subsite has its own numbered database tables.
  11. A pretty permalink inside the subsite returns HTTP 200.
  12. Required plugins can be reactivated without breaking the main site or subsite.
  13. The temporary site can be removed cleanly.
  14. No important production URL changed unexpectedly.

Final thoughts

The command that converts WordPress to Multisite is the easy part:

wp core multisite-convert

The part that deserves attention is everything around it.

Check who owns the site. Check the database before changing it. Know which plugins are active. Record the existing URLs. Make sure the rewrite rules match a directory network. Create a real subsite. Test its admin area. Create a page inside it. Check the new database tables. Then reactivate plugins one by one.

When those pieces all work together, you have more than a Multisite flag in wp-config.php. You have a directory-based WordPress network that has actually been tested on the production stack.

Written by

Shah Alom

Leave a Reply

Your email address will not be published. Required fields are marked *