With WordPress as widespread as it is, it’s a wonder the lack of up-to-date resources on how to configure a secure environment for hosting WordPress websites.
This is our attempt at compiling the most recent best-practices based on the latest software packages as of this writing.
- Ubuntu 22.0.4 LTS (Jammy Jellyfish)
- NGINX 1.22.0
- MySQL 8
- PHP 8.1
For many reasons—related to performance, security, stability, flexibility and cost—we avoid Windows hosting at all costs.
We will be using the most recent version of Ubuntu 22.0.4 LTS, our preferred flavour of Linux, mainly due to the availability of online support when it comes to working through obscure dependency-related issues. While our setup is fairly straightforward, any future issues that arise are—in our experience—easier to resolve on Ubuntu since it’s so widespread.
Users tend to have multiple WordPress instances often hosted in the same environment. We will also assume that the environment that we are setting up is going to be shared across multiple WordPress installations, and therefore create separate chroot environments to host each of them them.
In the event of a breach of one website, this will help reduce the risk of cross-contamination to the other websites as the attackers will have write access within the chroot jail only and be prevented from reading the rest of the filesystem.
While the developers of the core WordPress platform tend to be very security conscious—the practices of the 3rd-party developers of the plugins that make it so popular, can tend to be a mixed bag.
We advise being very judicious when deciding to add a plugin, and to centralize the decision-making around this to one key person in the organization who is also responsible for timely updates of the plugins and WordPress core. If multiple people are installing plugins on a whim, they can get out of hand easily, and the risk of vulnerability through one of them increases.
Unused plugins and themes should be removed, and themes should always be developed as child themes of any installed base theme to allow for updating the dependency without overwriting the customizations of the child theme.
Install Ubuntu 22.0.4 LTS
We won’t labour through the OS installation, except to point to resources such as Digital Ocean’s Initial Server Setup with Ubuntu 22.04. Key takeaways are:
- to set up non-root users with sudo access for administrators rather than directly accessing the root account
- disable password access in favour of SSH key-based authentication
Some additional housekeeping we like to do is:
- setup vi as your default editor (vi is essential for aspiring system administrators so it’s definitely worth learning as a prerequisite):
sudo update-alternatives --set editor /usr/bin/vim.basic
- add your administrator account to sudoers bypassing password entry (most of the commands you will require for this tutorial require sudo and this will make it easier on you)
sudo visudo
- add the following to the end of the file where username corresponds to your current administrator user:
username ALL=(ALL) NOPASSWD: ALL
- upgrade all of your packages right away:
sudo apt update
sudo apt full-upgrade
sudo apt autoremove
sudo reboot
Install MySQL 8
MySQL installation is straightforward:
sudo apt install mysql-server
Install PHP 8.1
PHP and its most common extensions for WordPress can be installed as follows:
sudo apt install php-fpm php-mysql php-curl php-gd php-intl php-mbstring php-soap php-xml php-xmlrpc php-zip
Create chroot environments
For the purposes of this tutorial, we are going to create our chroot environments in the following locations:
- /var/www/site1
- /var/www/site2
- …
Let’s start by creating the user for the first site:
sudo adduser --gecos "Sample Website" --disabled-password --home /var/www/site1 site1
Note that the following instructions are distilled from the article Secure webspaces with NGINX, PHP-FPM chroots and Let’s Encrypt.
First we want to configure the corresponding chroot jail:
sudo vi /etc/php/8.1/fpm/pool.d/site1.conf
- add the following lines to the file:
[site1]
user = $pool
group = $pool
listen = /run/php/php8.1-fpm-$pool.sock
listen.owner = nginx
listen.group = nginx
php_admin_value[disable_functions] = exec,passthru,shell_exec,system
php_admin_flag[allow_url_fopen] = off
pm = dynamic
pm.max_children = 5
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 3
chroot = /var/www/$pool
chdir = /data
sudo service php8.1-fpm restart
sudo curl -o /usr/local/sbin/php-chroot-bind https://raw.githubusercontent.com/68b32/php-chroot-bind/master/php-chroot-bind
sudo sed -i 's/ls -1d \/home\/www\/*\/chroot/cat \/var\/www\/chroot.list/' /usr/local/sbin/php-chroot-bind
sudo chmod u+x /usr/local/sbin/php-chroot-bind
sudo echo /var/www/site1 > /var/www/chroot.list
sudo crontab -e
- append the following line to the file:
@reboot /usr/local/sbin/php-chroot-bind bind
sudo /usr/local/sbin/php-chroot-bind bind
Install NGINX 1.22.0
Our preferred version of NGINX is the most recent mainline version. The full NGINX setup tutorial is available here but use these commands for the current configuration:
sudo vi /etc/apt/sources.list.d/nginx.list
- Add the following lines to the file:
deb https://nginx.org/packages/ubuntu/ jammy nginx
deb-src https://nginx.org/packages/ubuntu/ jammy nginx
sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys ABF5BD827BD9BF62
sudo apt update
sudo apt install nginx
Some housekeeping is needed to keep things organized:
sudo mkdir /etc/nginx/sites-available
sudo mv /etc/nginx/conf.d/default.conf /etc/nginx/sites-available
sudo vi /etc/nginx/sites-available/site1.conf
- add the following lines to the file, updating the domain as required:
server {
server_name site1.example.com;
server_tokens off;
root /var/www/site1/data;
index index.php index.html index.htm;
error_log /var/log/nginx/site1.error.log;
access_log /var/log/nginx/site1.access.log;
location ~* ^/(?:xmlrpc\.php|wp-links-opml\.php|wp-config\.php|wp-config-sample\.php|readme\.html|license\.txt|nginx\.conf|\.user\.ini|\.ht.*)$ {
deny all;
}
location / {
try_files $uri $uri/ /index.php?$args;
}
location ~ [^/]\.php(/|$) {
include fastcgi_params;
fastcgi_pass unix:/var/run/php/php8.1-fpm-site1.sock;
fastcgi_param SCRIPT_FILENAME /data$fastcgi_script_name;
}
}
sudo ln -s /etc/nginx/sites-available/site1.conf /etc/nginx/conf.d/site1.conf
Install Certbot
We will be serving our websites through encrypted HTTPS so lets install Certbot as follows:
sudo apt install certbot python3-certbot-nginx
- Update the domain as required below:
sudo certbot --agree-tos --redirect --nginx -d site1.example.com
If you’ve made it this far, you’re ready to set up WordPress inside the /var/www/site1/data
directory. Note that you should also ensure you configure a firewall, harden your NGINX and PHP configurations, and all of the other prerequisites found in the How To Install WordPress on Ubuntu 22.04 with a LAMP Stack article. One additional note is to set up your database host during setup, or the DB_HOST
variable inside your wp-config.php
to 127.0.0.1
instead of localhost.
Have any questions or feedback on this tutorial? Connect with us at the contact form below.
Need expert system administration, remediation and hosting support services for WordPress? Please reach out to us.