WordPress Multisite lets one WordPress installation run a network of sites that share the same code, plugins, themes and database, while each site keeps its own content and users. In this tutorial you will convert an existing WordPress site running on Nginx and PHP-FPM on Ubuntu 24.04 into a Multisite network with WP-CLI, configure Nginx for either subdomain or subdirectory mode, add TLS, and create and manage your first sites.
Prerequisites
To follow this tutorial, you will need:
- A server running Ubuntu 24.04 LTS, for example a CubePath VPS, with a non-root user that has
sudoprivileges. - A working WordPress site served by Nginx and PHP 8.3-FPM, installed in
/var/www/your_domain. Replaceyour_domainwith your real domain throughout this guide. - A domain whose DNS you control, with an A record for
your_domainpointing toyour_server_ip. - Pretty permalinks enabled in Settings > Permalinks (any option other than "Plain").
Step 1 - Choosing subdomains or subdirectories
Multisite offers two URL layouts, and you cannot easily switch between them after the network is created:
| Mode | Site URLs | DNS and TLS | Best for |
|---|---|---|---|
| Subdomains | shop.your_domain, blog.your_domain | Wildcard A record and wildcard certificate | Established sites, independent brands or regions |
| Subdirectories | your_domain/shop, your_domain/blog | Nothing extra, one certificate | New installs, departments of one organization |
The WordPress Network Setup screen only offers subdirectories on installs younger than one month, because a new site slug such as /shop can collide with an existing page URL. If your site already has content, subdomains are the safer choice. Subdirectories still work if you pick slugs that no existing page or post uses.
Step 2 - Installing WP-CLI and backing up the site
WP-CLI converts the site in one command and is the fastest way to manage the network later. Download the official Phar and check that it runs:
curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar
php wp-cli.phar --info
Make it executable and move it into your PATH:
chmod +x wp-cli.phar
sudo mv wp-cli.phar /usr/local/bin/wp
All the WP-CLI commands in this guide run as the www-data user, which owns the WordPress files, from the WordPress directory:
cd /var/www/your_domain
sudo -u www-data wp core version
6.8.2
Converting to Multisite adds new tables and changes wp-config.php, so take a database and file backup first:
sudo -u www-data wp db export /tmp/pre-multisite.sql
sudo tar -czf ~/wordpress-files-pre-multisite.tar.gz -C /var/www your_domain
sudo mv /tmp/pre-multisite.sql ~/
Check that both files exist and are not empty:
ls -lh ~/pre-multisite.sql ~/wordpress-files-pre-multisite.tar.gz
Step 3 - Converting the site to a network
The Network Setup process expects all plugins to be deactivated. Save the list of active plugins so you can reactivate them afterwards, then deactivate them:
sudo -u www-data wp plugin list --status=active --field=name | tee ~/active-plugins.txt
sudo -u www-data wp plugin deactivate --all
Now run the conversion. Use --subdomains for subdomain mode, or omit it for subdirectory mode:
sudo -u www-data wp core multisite-convert --title="Your Network" --subdomains
Set up multisite database tables.
Added multisite constants to wp-config.php.
Success: Network installed. Don't forget to set up rewrite rules (and a .htaccess file, if using Apache).
Open wp-config.php and confirm that WP-CLI added the Multisite constants above the line /* That's all, stop editing! Happy publishing. */:
sudo nano /var/www/your_domain/wp-config.php
define( 'WP_ALLOW_MULTISITE', true );
define( 'MULTISITE', true );
define( 'SUBDOMAIN_INSTALL', true );
$base = '/';
define( 'DOMAIN_CURRENT_SITE', 'your_domain' );
define( 'PATH_CURRENT_SITE', '/' );
define( 'SITE_ID_CURRENT_SITE', 1 );
define( 'BLOG_ID_CURRENT_SITE', 1 );
In subdirectory mode SUBDOMAIN_INSTALL is false. If wp-config.php was not writable, WP-CLI prints these lines instead and you must paste them in yourself.
DOMAIN_CURRENT_SITE must match the domain in your Site Address exactly, without www or https://. A mismatch here is the most common cause of redirect loops after conversion.
Reactivate the plugins you saved earlier on the main site:
xargs -a ~/active-plugins.txt sudo -u www-data wp plugin activate
Step 4 - Configuring Nginx
Nginx does not read .htaccess, so the rewrite rules WordPress mentions must go into the server block. Open your site's configuration:
sudo nano /etc/nginx/sites-available/your_domain
Subdirectory mode
Subdirectory sites request files such as /shop/wp-admin/ and /shop/wp-includes/js/... that physically live in the WordPress root. The following rewrites strip the site slug so Nginx can find them. This is the full server block:
server {
listen 80;
listen [::]:80;
server_name your_domain www.your_domain;
root /var/www/your_domain;
index index.php;
if (!-e $request_filename) {
rewrite /wp-admin$ $scheme://$host$request_uri/ permanent;
rewrite ^(/[^/]+)?(/wp-.*) $2 last;
rewrite ^(/[^/]+)?(/.*\.php) $2 last;
}
location / {
try_files $uri $uri/ /index.php?$args;
}
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
}
location ~ /\.(?!well-known) {
deny all;
}
}
Subdomain mode
Subdomain sites need no rewrites, but Nginx must answer for every subdomain. Add *.your_domain to server_name, and redirect www to the bare domain in its own server block, because WordPress would otherwise treat www as a subsite that does not exist:
server {
listen 80;
listen [::]:80;
server_name www.your_domain;
return 301 $scheme://your_domain$request_uri;
}
server {
listen 80;
listen [::]:80;
server_name your_domain *.your_domain;
root /var/www/your_domain;
index index.php;
location / {
try_files $uri $uri/ /index.php?$args;
}
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
}
location ~ /\.(?!well-known) {
deny all;
}
}
Nginx gives exact names priority over wildcards, so www.your_domain still matches the redirect block. Test the configuration and reload:
sudo nginx -t
sudo systemctl reload nginx
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
Step 5 - Setting up DNS and TLS
Subdirectory mode
No DNS changes are needed. If your site does not have a certificate yet, request one with Certbot's Nginx plugin:
sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d your_domain -d www.your_domain
Subdomain mode
Create a wildcard record at your DNS provider so every subdomain reaches the server:
| Type | Name | Value |
|---|---|---|
| A | * | your_server_ip |
Check that an arbitrary subdomain resolves:
dig +short test123.your_domain
your_server_ip
Let's Encrypt only issues wildcard certificates through the DNS-01 challenge, which requires a Certbot DNS plugin for your provider. With Cloudflare, install the plugin:
sudo apt install certbot python3-certbot-nginx python3-certbot-dns-cloudflare
Create a Cloudflare API token with the Zone > DNS > Edit permission for your zone, and store it in a file only root can read:
sudo mkdir -p /root/.secrets
sudo nano /root/.secrets/cloudflare.ini
dns_cloudflare_api_token = your_cloudflare_api_token
sudo chmod 600 /root/.secrets/cloudflare.ini
Request a certificate that covers the bare domain and all subdomains, then let the Nginx plugin install it into your server blocks:
sudo certbot certonly --dns-cloudflare --dns-cloudflare-credentials /root/.secrets/cloudflare.ini -d your_domain -d '*.your_domain'
sudo certbot install --nginx --cert-name your_domain
Certbot renews wildcard certificates automatically with the same DNS plugin. Confirm renewal works:
sudo certbot renew --dry-run
If you use another DNS provider, look for its python3-certbot-dns-* package or a Certbot plugin from the provider.
Finally, make sure the network uses HTTPS URLs. Update the main site's addresses if they still start with http://:
sudo -u www-data wp search-replace 'http://your_domain' 'https://your_domain' --network --skip-columns=guid --dry-run
Review the count of replacements, then run the same command without --dry-run.
Step 6 - Creating sites in the network
Create a site with WP-CLI. The --slug becomes the subdomain (shop.your_domain) or the subdirectory (your_domain/shop):
sudo -u www-data wp site create --slug=shop --title="Shop" --email=admin@your_domain
Success: Site 2 created: https://shop.your_domain/
List the sites in the network:
sudo -u www-data wp site list --fields=blog_id,url
+---------+---------------------------+
| blog_id | url |
+---------+---------------------------+
| 1 | https://your_domain/ |
| 2 | https://shop.your_domain/ |
+---------+---------------------------+
Visit the new URL in a browser. You can also create sites from My Sites > Network Admin > Sites > Add New, which is available at https://your_domain/wp-admin/network/.
Most WP-CLI commands accept --url to target a specific site. For example, to change the tagline of the shop site:
sudo -u www-data wp option update blogdescription "Our online store" --url=shop.your_domain
Step 7 - Managing plugins, themes and users
In a network, only Super Admins can install plugins and themes. Site administrators can then activate what the network allows.
Install a plugin and activate it on every site at once with --network:
sudo -u www-data wp plugin install akismet --activate-network
To leave the choice to each site, install the plugin without network activation; site administrators can activate it from their own Plugins screen once you enable plugin menus in Network Admin > Settings > Menu Settings.
Themes must be network-enabled before any site can use them. Enable a theme for the whole network and activate it on one site:
sudo -u www-data wp theme enable twentytwentyfive --network
sudo -u www-data wp theme activate twentytwentyfive --url=shop.your_domain
Create a user who only belongs to the shop site, with the editor role:
sudo -u www-data wp user create jane jane@your_domain --role=editor --url=shop.your_domain
Add an existing user to another site, and grant Super Admin rights to a trusted administrator:
sudo -u www-data wp user set-role jane author --url=your_domain
sudo -u www-data wp super-admin add your_admin_user
sudo -u www-data wp super-admin list
Keep the number of Super Admins small: they can install code that runs on every site in the network.
Step 8 - Running WP-Cron for every site
WP-Cron only fires when someone visits a site, and each site in the network has its own schedule. Low-traffic subsites therefore miss scheduled posts and updates. Disable the visit-based trigger by adding this line to wp-config.php, above the "stop editing" comment:
define( 'DISABLE_WP_CRON', true );
Then run due events for every site from the system cron. Create a cron file:
sudo nano /etc/cron.d/wordpress-multisite
*/5 * * * * www-data /usr/local/bin/wp --path=/var/www/your_domain site list --field=url | xargs -I{} /usr/local/bin/wp --path=/var/www/your_domain cron event run --due-now --url={} >/dev/null 2>&1
Run the same pipeline once by hand to verify it works:
sudo -u www-data wp site list --field=url | xargs -I{} sudo -u www-data wp cron event run --due-now --url={}
Success: Executed a total of 2 cron events.
Success: Executed a total of 1 cron event.
Troubleshooting
- Redirect loop or "Error establishing a database connection" after conversion: check that
DOMAIN_CURRENT_SITEandPATH_CURRENT_SITEinwp-config.phpmatch thedomainandpathcolumns in thewp_siteandwp_blogstables. Runsudo -u www-data wp db query "SELECT domain, path FROM wp_blogs;"to compare. - Subdirectory sites load without CSS or return 404 on
/site/wp-admin/: the rewrite block from Step 4 is missing or placed after alocationthat already matched. Keep theifblock at server level. - New subdomain shows the Nginx default page: the wildcard
server_nameis missing, or the wildcard DNS record has not propagated yet. Check withdig +short newsite.your_domain. - Certificate warning on subsites: the certificate only covers
your_domain. Runsudo certbot certificatesand confirm*.your_domainappears in the list of domains.
Conclusion
You converted a single WordPress site into a Multisite network, configured Nginx for subdomains or subdirectories, secured it with TLS, and created sites, users and network-wide plugins from WP-CLI. As next steps, add a persistent object cache such as Redis, which helps networks with many sites, set up automated database and file backups that cover all sites, and review which plugins you allow site administrators to activate.
