Privacy Security

Configuring a Secure WordPress LEMP Stack with chroot Jails

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:
user = $pool
group = $pool
listen = /run/php/php8.1-fpm-$pool.sock
listen.owner = nginx = 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
  • 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 jammy nginx
deb-src jammy nginx
  • sudo apt-key adv --keyserver --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_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

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 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.