Securing WordPress: Unprivileged LXC Container on Ubuntu

Setting up Apache2, MySQL, PHP

DigitalOcean has published a detailed article1 on setting up your LAMP (Linux, Apache2, MySQL, PHP) stack. These are needed as WordPress runs on PHP and uses MySQL database. Here is a quick run-through of the bare essentials.

  1. Let’s install Apache.
root> apt install apache2

Apache2 should be installed and have a test page available on port 80 through our static IP address. We will be testing it shortly.

  1. Let’s get MySQL and add a password. First, let’s install it:
root> install mysql-server
root> mysql_secure_installation

You will be asked if you wish to configure a plugin to improve password security. We will be skipping this since we are only using one database user just for WordPress.

By default, root user of the system can automatically authenticate themselves to MySQL based on their UID through auth_socket plugin. We can see this by opening MySQL prompt (notice that no password is required), and pull up the list of authentication method used by each account.

root> mysql
mysql> SELECT user,authentication_string,plugin,host FROM mysql.user;

Then, we can configure the root account to use a password “ChangeMe” and apply new changes as follows:

mysql> ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'ChangeMe';

We are done with MySQL and we can exit mysql shell now.

mysql> exit
  1. And finally, PHP. We will install PHP as well as modules / helper packages to integrate PHP with Apache2, MySQL database, and WordPress.
root> apt install php libapache2-mod-php php-mysql php-curl php-gd php-mbstring php-xml php-xmlrpc php-soap php-intl php-zip

That’s it for PHP! You can additionally check your PHP installation with phpinfo(); but remember to delete this off. This function creates a lot of sensitive information about your server and environment that you probably don’t want attackers to find out.

If you want to configure Apache2 to prefer PHP files over others, modify the /etc/apache2/mods-enabled/dir.conf file to place index.php first so it looks like so:

<IfModule mod_dir.c>
    DirectoryIndex index.php index.html index.cgi index.xhtml index.htm

We are all done! To finish off the installation, let’s restart Apache2. We can check the status of the service with the second line.

root> systemctl restart apache2
root> systemctl status apache2

Setting up Apache2 Reverse Proxy

Now let us set up Apache2 on our host to fetch request from the Apache in our container. We will see if we can reach the test page generated from the Apache in the container.

  1. Enable Apache2 proxy modules in our host machine.
host> sudo a2enmod proxy
host> sudo a2enmod proxy_http
host> sudo a2enmod headers
  1. Configure your Apache2 in host machine.
host> sudo vim /etc/apache2/sites-enabled/000-default.conf

Modify the file to match something like below:

<VirtualHost *:443>
    # Listen to this name
    # For identifying the original protocol and port client used
    RequestHeader set X-Forwarded-Proto "https"
    RequestHeader set X-Forwarded-Port "443"

    # Set up proxy
    ProxyPreserveHost On
    ProxyPass /
    ProxyPassReverse /

    # Some boilerplate config for TLS
    SSLEngine On
    SSLCertificateFile "/var/www/"
    SSLCertificateKeyFile "/var/www/"
  • X-Forwarded-Proto and X-Forwarded-Port passes on information about the protocol and port from the original request. This is important later for WordPress to correctly choose the protocol to use. Recall that clients visit on HTTPS (port 443) but the request is relayed with HTTP (port 80).
  • ProxyPreserveHost preserves and retains the hostname from the original request.
  • ProxyPass is our reverse proxy at work. The Apache2 is passing on a path from to
  • ProxyPassReverse is how our Apache in the container affects the Apache in the host. If WordPress decides to head to, our main Apache in the host will understand to keep our domain and head to; not actually
  1. Let’s restart Apache2 for our changes and new modules to take effect.
host> sudo systemctl restart apache2

The reverse proxy should now be working. Visiting should show you the Apache test page from within the container! If this subdomain is not responding, remember to add it to your DNS entries and allow up to two days for it to take effect (DNS propagation).

Apache2 default test page

On the next page, we will set up WordPress.


  1. On Page 1, we start with a bit of literature so we know what we will be doing later on.
  2. On Page 2, we create an unprivileged LXC container.
  3. On Page 3, we set up Apache2, MySQL and PHP in the container. We also set up our main Apache2 as a reverse proxy to serve pages from within the container.
  4. On Page 4, we set up WordPress inside the container.
  5. On Page 5, we end with some closing thoughts.



Leave a Reply

Your email address will not be published. Required fields are marked *