Securing WordPress: Unprivileged LXC Container on Ubuntu

Let’s Get Started


It is recommended that the LXC be instantiated by an unprivileged user, but yet certain commands can only be issued with sudo. We will also need to interact with the container itself, which brings the total number of roles you have to play to three:

  • the user on the host with sudo privilege
  • the user on the host without sudo privilege
  • the root in the container

They will be denoted respectively as follows:

host> code here
lxc> code here
root> code here

Setting up LXC

  1. Let us install the utilities needed to make LXC containers.
host> sudo apt update && sudo apt install lxc-utils
  1. Create an unprivileged user called lxc and switch to that user.
host> sudo adduser lxc
host> su - lxc
  1. Fix environmental variables. Otherwise you might have issues creating containers later.
  1. Find out the namespaces allowed for the user lxc. The wildcard in the command below will match both suid and guid files. We will map the LXC container to these regions.
lxc> cat /etc/s*id|grep $USER

From the output, we can see that both assigned suid and guid values start at 231072 and runs through the next 65536 integers.

  1. Let us set up configuration files as required by LXC before starting a container. The required file is in ~/.config/lxc/default.conf. I am using vim as my text editor throughout this tutorial, but you can use nano if you wish.
lxc> mkdir -p ~/.config/lxc/
lxc> vim ~/.config/lxc/default.conf

Add in the following lines. Notice that the syntax might be different from what you find online. I am using the latest version of LXC, v3.0.3.

  • We are setting the network interface type to be veth and creating a new bridge lxcbr0. We will come back to these later.
  • Your hwaddr may differ; you can see the default values in /etc/lxc/default.conf.
  • uid and gid mapping. Starts from 231072 and runs for 65536 integers. Replace these with what you get from Step 4. = veth = lxcbr0 = up = 00:16:3e:xx:xx:xx
lxc.idmap = u 0 231072 65536
lxc.idmap = g 0 231072 65536
  1. Next, we place the limits on the network interface by changing the file in /etc/lxc/lxc-usernet.
host> sudo vim /etc/lxc/lxc-usernet

Add in the following line. This means that for the user lxc, allow up to 10 veth network interfaces (only veth is supported for now) to be attached to the bridge lxcbr0.

lxc veth lxcbr0 10
  1. Now, let us set a static IP for our container. This is necessary because our main Apache2 needs to know where to forward the requests.
host> sudo vim /etc/default/lxc-net

Key in the following. Here, we are assigning the hostname to a private IP address of Change the hostname accordingly and choose an IP address that isn’t used. If this IP is taken, you will be assigned another IP silently.,

Let’s make sure that the new configs are accepted by restarting the services.

host> sudo service lxc-net restart
host> sudo killall -s SIGHUP dnsmasq
  1. We are ready to create a container! Remember to head back to our unprivileged user.
  • -n for container name. This container name will automatically be used as our hostname later, so key in your domain as is.
  • -t for template. We will be downloading fresh from the web.
lxc> lxc-create -n -t download

You will be given a list of the available distros. You will be asked to choose the distribution, release and architecture. I am going with the same setup as my host. Choose the wrong architecture and your container will not start later! Here is how you can find information on your current setup.

host> uname -m
host> dpkg --print-architecture

A waiting time of a few cups of coffee later, the container should be successfully created!

  1. Let’s do a quick sanity check before starting the container. You can do one of these. The second lists all containers you may have with more information, including the IP address. (This is empty now but we can check later.)
lxc> lxc-info -n
lxc> lxc-ls -f

Let’s start the container! We specify the name of the container with -n and saves a debug log file for easy troubleshooting if something goes wrong.

lxc> lxc-start -n --logfile $HOME/lxc_blog.log --logpriority DEBUG

The container should be started! Feel free to lxc-info it again and verify that it is indeed RUNNING. Again, you might not see the IP address until there is some network activity.

  1. Finally, let’s attach to this container so we have a shell interface.
lxc> lxc-attach -n

You should be granted root without having to enter any credentials. It is now a good time to be setting a password for your root.

root> passwd

Since your distro was freshly downloaded, it should be up-to-date. However, you can force some network activity by getting curl. We need it later to download WordPress.

root> apt update && apt install curl

Your terminal is probably taken over by this container for now. You can launch another terminal and check again with lxc-ls -f of Step 9 and make sure the IP address is correct. Otherwise, stop the container and try another IP address (revisit Step 7). Remember to restart the network services as explained in Step 7.

Congratulations! You have created an unprivileged LXC! On the next page, we will install Apache2 in the container, and set up our main Apache2 to act as a reverse proxy.


  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 *