Are you a WordPress user who want to switch to WordPress Multisite Network setup? – To host more than one domain on the same WordPress instance?. In this guide, I’ll take you through the steps to setup WordPress Multisite Network using Nginx as a web server and secure the setup with Let’s Encrypt SSL certificates on Ubuntu 20.04|Debian 10 Linux systems.

In a nutshell, this is a LEMP Stack with WordPress installed and secured with Let’s Encrypt. So we will start our installation by setting up LEMP stack on Ubuntu 20.04/18.04, Debian 10/9 Linux. WordPress Multisite is a popular feature of WordPress which enables Web masters to run multiple websites using the same WordPress installation on the same server.

Step 1: Install MariaDB Database Server

This setup requires a database server. We will install and create a database to be used by WordPress. Run the commands below to install MariaDB database server:

sudo apt update
sudo apt -y install mariadb-server

After installation of the database server you need to secure it by running the command below:

$ sudo mysql_secure_installation

NOTE: RUNNING ALL PARTS OF THIS SCRIPT IS RECOMMENDED FOR ALL MariaDB
      SERVERS IN PRODUCTION USE!  PLEASE READ EACH STEP CAREFULLY!

In order to log into MariaDB to secure it, we'll need the current
password for the root user.  If you've just installed MariaDB, and
you haven't set the root password yet, the password will be blank,
so you should just press enter here.

Enter current password for root (enter for none):
OK, successfully used password, moving on...

Setting the root password ensures that nobody can log into the MariaDB
root user without the proper authorisation.

Set root password? [Y/n] y
New password:
Re-enter new password:
Password updated successfully!
Reloading privilege tables..
 ... Success!


By default, a MariaDB installation has an anonymous user, allowing anyone
to log into MariaDB without having to have a user account created for
them.  This is intended only for testing, and to make the installation
go a bit smoother.  You should remove them before moving into a
production environment.

Remove anonymous users? [Y/n] y
 ... Success!

Normally, root should only be allowed to connect from 'localhost'.  This
ensures that someone cannot guess at the root password from the network.

Disallow root login remotely? [Y/n] y
 ... Success!

By default, MariaDB comes with a database named 'test' that anyone can
access.  This is also intended only for testing, and should be removed
before moving into a production environment.

Remove test database and access to it? [Y/n] y
 - Dropping test database...
 ... Success!
 - Removing privileges on test database...
 ... Success!

Reloading the privilege tables will ensure that all changes made so far
will take effect immediately.

Reload privilege tables now? [Y/n] y
 ... Success!

Cleaning up...

All done!  If you've completed all of the above steps, your MariaDB
installation should now be secure.

Thanks for using MariaDB!

Step 2: Create database and user for WordPress

After the MariaDB server has been installed, proceed to create a database and user for your WordPress website.

But first update authentication plugin:

$ sudo mysql -u root
UPDATE mysql.user SET plugin = 'mysql_native_password' WHERE User = 'root';
FLUSH PRIVILEGES;
QUIT;

Login to your MariaDB database as root user:

$ mysql -u root -p
Enter password:
Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MariaDB connection id is 59
Server version: 10.3.29-MariaDB-0ubuntu0.20.04.1 Ubuntu 20.04

Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MariaDB [(none)]>

Then create a database for wordpress:

CREATE DATABASE wp_db;
GRANT ALL PRIVILEGES ON wp_db.* TO  wp_user IDENTIFIED BY "[email protected]";
FLUSH PRIVILEGES;
\q

Once the database is ready, proceed to install nginx web server:

Step 3: Install Nginx Web Server

Next we install Nginx web server on our Debian / Ubuntu server:

sudo apt install nginx

Starting nginx service on both methods is from systemd service manager.

sudo systemctl start nginx

Enable service to start on boot using:

sudo systemctl enable nginx

Step 4: Install PHP and PHP-FPM process handler

The next phase is the installation of php and all required modules. Unlike Apache web server, Nginx does not contain native PHP processing. For that, we have to install PHP-FPM (FastCGI Process Manager).On Ubuntu and Debian, install php and php-fpm using the commands:

sudo apt update
sudo apt install php php-{fpm,pear,cgi,common,mbstring,net-socket,gd,xml-util,mysql,bcmath}

PHP socket is located on /var/run/php/ directory, i.e 

  • For PHP 7.4 – /var/run/php/php7.4-fpm.sock, you can confirm this on /etc/php/7.4/fpm/pool.d/www.conf.
  • For PHP 7.2 – /var/run/php/php7.2-fpm.sock
$ sudo vim /etc/php/*/fpm/pool.d/www.conf

Step 5: Download and Install WordPress

Download WordPress archive:

wget wordpress.org/latest.tar.gz

Extract the file:

tar xvf latest.tar.gz

Move resulting wordpress folder to website root directory:

sudo mv wordpress /var/www/mywebsite

Configure WordPress Database connection

cd /var/www/mywebsite
sudo cp wp-config-sample.php wp-config.php

Edit  wp-config.php:

$ sudo vim wp-config.php
define('DB_NAME', 'wp_db');
define('DB_USER', 'wp_user');
define('DB_PASSWORD', '[email protected]');

Change ownership of /var/www/mywebsite to web user:

sudo chown -R www-data:www-data /var/www/mywebsite

Step 6: Configure Nginx for WordPress Multisite Network

Create VirtualHost configuration file for your WordPress:

sudo vim /etc/nginx/conf.d/mywebsite.conf

Copy the following configuration snippet and modify accordingly:

##################################
# WORDPRESS NGINX CONFIGURATIONS
##################################

server {
        listen 80;
        root /var/www/mywebsite;
        server_name mywebsite.com;
        return 301 https://$server_name$request_uri;
}

server {
    	listen 443 ssl http2;
        root /var/www/mywebsite;
	access_log /var/log/nginx/wp_client_access.log;
	error_log /var/log/nginx/wp_client_error.log;
        server_name mywebsite.com;
	ssl_certificate /etc/letsencrypt/live/mywebsite.com/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/mywebsite.com/privkey.pem;

# Attempt to rewrite wordpress in sub directory 
rewrite ^/wp/([_0-9a-zA-Z-]+)/(xmlrpc\.php|wp-[0-9a-z-]+\.php) /wp/$2;
rewrite ^/wp/([_0-9a-zA-Z-]+)/(wp-(admin|content|includes).*) /wp/$2;


location / {
    index                               index.php index.html;
    try_files                           $uri $uri/ /index.php?$args;
}


#############
# Specify a charset
############
        charset                         utf-8;

############
# GZIP
###########

        gzip                            off;

#############
# Add trailing slash to */wp-admin requests.
############

        rewrite /wp-admin$ $scheme://$host$uri/ permanent;


############
# this prevents hidden files (beginning with a period) from being served
############

location ~ /\. {
        access_log                      off;
        log_not_found                   off;
        deny                            all;
}

###########
# SEND EXPIRES HEADERS AND TURN OFF 404 LOGGING
###########

        location ~* ^.+.(xml|ogg|ogv|svg|svgz|eot|otf|woff|mp4|ttf|css|rss|atom|js|jpg|jpeg|gif|png|ico|zip|tgz|gz|rar|bz2|doc|xls|exe|ppt|tar|mid|midi|wav|bmp|rtf)$ {
        access_log                      off;
        log_not_found                   off;
        expires                         max;
}

############
# Pass uploaded files to wp-includes/ms-files.php.
############

#       rewrite                         /files/$ /index.php last;

if ($uri !~ wp-content/plugins) {
        rewrite /files/(.+)$ /wp-includes/ms-files.php?file=$1 last;
}

# Rewrite multisite in a subdirectory '.../wp-.*' and '.../*.php'.
# if (!-e $request_filename) {
#    rewrite ^/[_0-9a-zA-Z-]+(/wp-.*) $1 last;
#    rewrite ^/[_0-9a-zA-Z-]+.*(/wp-admin/.*\.php)$ $1 last;
#    rewrite ^/[_0-9a-zA-Z-]+(/.*\.php)$ $1 last;
#}

# Rewrite multisite '.../wp-.*' and '.../*.php'.
if (!-e $request_filename) {
    rewrite /wp-admin$ $scheme://$host$uri/ permanent;
    rewrite ^/[_0-9a-zA-Z-]+(/wp-.*) /wp$1 last;
    rewrite ^/[_0-9a-zA-Z-]+(/.*\.php)$ /wp$1 last;
}


############
# Pass all .php files onto a php-fpm or php-cgi server
############

location ~ \.php$ {

        # Try the files specified in order. In our case, try the requested URI and if
        # that fails, try (successfully) to pass a 404 error.
        # zero day exploit defense

        try_files                       $uri =404;

        # Include the fastcgi_params defaults provided by nginx

        include                         /etc/nginx/fastcgi_params;

        # The amount of time for upstream to wait for a fastcgi process to send data.
        # We keep this *extremely* high so that one can be lazy when remote debugging.

        fastcgi_read_timeout            3600s;
        
         # Buffer size for reading the header of the backend FastCGI process.
        # This defaults to the value of a single fastcgi_buffers, so does not
        # need to be specified in our case, but it's good to be explicit.

        fastcgi_buffer_size             128k;

        # The number and size of the buffers into which the reply from the FastCGI
        # process in the backend is read.
        #
        # 4 buffers at 128k means that any reply by FastCGI greater than 512k goes
        # to disk and replies under 512k are handled directly in memory.

        fastcgi_buffers                 4 128k;

        # SCRIPT_FILENAME is a required parameter for things to work properly,
        # but was missing in the default fastcgi_params on upgrade to nginx 1.4.
        # We define it here to be sure that it exists.

        fastcgi_param                   SCRIPT_FILENAME $document_root$fastcgi_script_name;


 # Use the upstream for php7.0-fpm that we defined in nginx.conf

        fastcgi_pass                    unix:/run/php/php7.4-fpm.sock;
        #fastcgi_pass                    127.0.0.1:9000;

        # And get to serving the file!

        fastcgi_index                   index.php;
}


############
# ROBOTS
###########

         location = /robots.txt {
               allow all;
               log_not_found off;
               access_log off;
        }


############
# RESTRICTIONS
############

# Deny access to any files with a .php extension in the uploads directory
# Works in sub-directory installs and also in multisite network
# Keep logging the requests to parse later (or to pass to firewall utilities such as fail2ban)
location ~* /(?:uploads|files)/.*\.php$ {
 deny all;
}

}

Remember to replace mywebsite.com with your valid domain name,/var/www/mywebsite with your wordpress installation root.

Step 7: Generate Let’s Encryt SSL certificate for the domain

Once you have modified nginx with correct settings, proceed to request for Letsencrypt certificate that we’ll use to secure the main site. Download and install certbot client to use:

sudo apt update
sudo apt install certbot

Open http and https ports on the firewall if ufw is active:

sudo ufw allow http
sudo ufw allow https

Stop nginx:

sudo systemctl stop nginx

Request for certificate

export DOMAIN="mywebsite.com"
export EMAIL="[email protected]"
sudo certbot certonly --standalone -d $DOMAIN --preferred-challenges http --agree-tos -n -m $EMAIL --keep-until-expiring

You should get output like this:

Saving debug log to /var/log/letsencrypt/letsencrypt.log
Plugins selected: Authenticator standalone, Installer None
Obtaining a new certificate
Performing the following challenges:
http-01 challenge for wp.computingforgeeks.com
Waiting for verification...
Cleaning up challenges

IMPORTANT NOTES:
 - Congratulations! Your certificate and chain have been saved at:
   /etc/letsencrypt/live/mywebsite.com/fullchain.pem
   Your key file has been saved at:
   /etc/letsencrypt/live/mywebsite.com/privkey.pem
   Your cert will expire on 2018-09-17. To obtain a new or tweaked
   version of this certificate in the future, simply run certbot-auto
   again. To non-interactively renew *all* of your certificates, run
   "certbot-auto renew"
 - If you like Certbot, please consider supporting our work by:

   Donating to ISRG / Let's Encrypt:   https://letsencrypt.org/donate
   Donating to EFF:                    https://eff.org/donate-le

Confirm that certs we indeed generated:

$ sudo ls -1 /etc/letsencrypt/live/mywebsite.com/
cert.pem
chain.pem
fullchain.pem
privkey.pem
README

Make sure you modify nginx configuration ssl section to point to correct path of Let’s Encrypt private key and certificate.

Validate configuration settings then restart nginx for the changes to be affected:

$ sudo nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

$ sudo systemctl restart nginx

Step 8: Configure WordPress Website from browser

Open http://mywebsite.com to start wordpress installation. You should get initial installation page which looks similar to one below.

wordpress multisite install 01

Provide required information and click on “Install WordPress” button. If all goes well, you should get login page.

wordpress multisite install 02

Click Log in and provide username and password.

wordpress multisite install 03

On Login, you should get to wordpress Admin dashboard.

wordpress multisite install 04

Step 9: Configure WordPress Multisite

For WordPress multisite to work, you need to first enable it on wordpress configuration file:

sudo vim /var/www/mywebsite.com/wp-config.php

Add the following content before the line /* That's all, stop editing! Happy blogging. */

define( 'WP_ALLOW_MULTISITE', true );

Restart Nginx service:

sudo systemctl restart nginx

Relogin to WordPress Admin Page and go to:

Tools > Network Setup

wordpress multisite install 05

Choose whether to use Sub-domains or sub-directories to host other sites.

Enabling WordPress Network

Paste the given configuration snippet on /var/www/mywebsite/wp-config.php, just before /* That's all, stop editing! Happy blogging. */

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

See below screenshot as example:

wordpress multisite install 06

Restart nginx:

sudo systemct restart nginx

Relogin to start using WordPress Network Multisite feature. You should see new Network Admin Menu.

wordpress multisite install 07

You can start adding websites to your WordPress Network by navigating to Sites > Add New

wordpress multisite install 08

In our next guide, I’ll cover how to Add websites to wordpress Multisite Network Setup. Stay connected to receive our latest article updates.

More on Web hosting:

Your support is our everlasting motivation,
that cup of coffee is what keeps us going!


As we continue to grow, we would wish to reach and impact more people who visit and take advantage of the guides we have on our blog. This is a big task for us and we are so far extremely grateful for the kind people who have shown amazing support for our work over the time we have been online.

Thank You for your support as we work to give you the best of guides and articles. Click below to buy us a coffee.

LEAVE A REPLY

Please enter your comment!
Please enter your name here