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 22.04|20.04 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 22.04|20.04 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.31-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 "StrongDBP@ssw0rd";
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 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 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
- PHP 8.0 Sock – /run/php/php8.0-fpm.sock
- For PHP 7.4 – /var/run/php/php7.4-fpm.sock
- For PHP 7.3 – /var/run/php/php7.3-fpm.sock
- 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', 'StrongDBP@ssw0rd');
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/php-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.geeksforgeeks.org
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.
Provide required information and click on “Install WordPress” button. If all goes well, you should get login page.
Click Log in and provide username and password.
On Login, you should get to wordpress Admin dashboard.
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
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:
Restart nginx:
sudo systemct restart nginx
Relogin to start using WordPress Network Multisite feature. You should see new Network Admin Menu.
You can start adding websites to your WordPress Network by navigating to Sites > Add New
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: