OpenVPN on Docker

A self-hosted VPN is a simple and secure way to access your home or small business network. For small businesses, this is a great way to set up a VPN connection to allow your employees to work remote. For the rest of us, it is also a great way to secure your Internet connection when using unsecured WiFi.

And for a self-hosted VPN solution, OpenVPN is one of the best solutions. One of the best known as well. It’s free and there are both desktop and mobile clients available.

Setting it up, however, isn’t nearly as straightforward. But that’s where Docker comes in. If you’ve never played around with it…. why not? It allows for very clean deployments and easy cleanup and upgrades without affecting the host system and anything else installed on it. As you’ll see here with deploying an OpenVPN instance.

In my instance I’m using Docker on Fedora 27 inside a virtual machine running on Proxmox, hosted on an old HP Z600 dual-Xeon workstation (the Xeons are from 2009, not anything to write home about). While the instructions for setting up the container are pretty straightforward, I’ll walk through some of the finer details based on my experience setting it up.

Before setting things up

Hopefully in your research on self-hosting a VPN you’ve discovered that you need a domain name for accessing it. So go to one of the several dynamic DNS hostname services and create a hostname for your home network. Without that hostname, you’re only setting yourself up for problems down the line trying to consistently use your VPN. So if you’re settled on self-hosting a VPN service, do that now.

Personally I use No-IP, and I’ve used them for… about 12 years now. While they do give one hostname for free, if you sign up for them, do yourself a favor and pay the $25 yearly subscription price so you don’t have to keep renewing your hostname every month.

Most home routers have built in support for dynamic DNS hostname services so it automatically updates your hostname with the IP address, so read the instructions on your router to set it up. Not all routers support all services, so use your router support to determine which service to select.

Fedora 27 and Docker

One thing needs to be said about using Docker with Fedora, though this kind of applies to all distros as well: do not use the Docker packages that come with Fedora. Instead follow the commands on Docker’s website to install the latest Docker Community Edition. Regardless of distribution, though, you’ll want to do this with their supported distributions.

The OpenVPN container does not play well with the Docker build distributed with Fedora 27. And that build is also several versions behind, and it’s always imperative to stay up to date.

With Docker installed, it’s time to pull the container and continue with the installation.

Note: If you will be running other Docker containers to which you want access over the VPN connection – e.g. a MySQL container for a GnuCash database – make sure the Docker bridge (e.g. docker0) is in the same firewall zone as the network adapter, otherwise the firewall will cut you off from being able to access them.

Installing OpenVPN on Docker

Installing OpenVPN is as simple as pulling the OpenVPN container and setting things up. If you’re familiar with Docker, you’ll notice right away the instructions in the container’s documentation are likely not familiar. I copied these instructions mostly verbatim from the container’s documentation to fill in a few details from my own experience.

# Pull the image
sudo docker pull kylemanna/openvpn
 
# Create the volume and set up the keys
OVPN_DATA="ovpn-data-home" # Call this whatever you want
sudo docker volume create $OVPN_DATA
 
# Create initial configuration
sudo docker run \
-v $OVPN_DATA:/etc/openvpn \
--rm kylemanna/openvpn ovpn_genconfig \
-u udp://[VPN.SERVERNAME.COM]

where, in the last command, VPN.SERVERNAME.COM is the DNS name for your home network. This would be the dynamic DNS name you created earlier.

Updating the configuration

The default configuration for the OpenVPN Docker image uses the Google DNS servers. This may not be desirable depending on what is available on your network that you want accessible – such as mapped drives from a NAS or other services.

So the configuration will need to be updated to push different DNS servers to clients. You need access to the configuration file in the volume. (Note: highlight, copy and paste the below code if the all underscores aren’t showing in your browser.)

cd /var/lib/docker/volumes/$OVPN_DATA/_data

The file to edit is “openvpn.conf”, and the lines you’re looking for are:

push "dhcp-option DNS 8.8.8.8"
push "dhcp-option DNS 8.8.4.4"

You’ll want to modify the DNS server IPs to whatever is used on your home network. You can tweak any other options as you feel necessary – that is well beyond the scope of this article. Just DO NOT touch the “proto” and “port” options.

Setting up certificates and client profiles

With the volume created, now to create the server certificate:

sudo docker run --rm \
-v $OVPN_DATA:/etc/openvpn \
-it kylemanna/openvpn ovpn_initpki

You will be prompted to create a passphrase for this certificate. So as always make sure to pick a reasonably secure passphrase since you’re securing the key used to generate the client profile for accessing your VPN. When asked for the “Common Name” for the certificates, use the hostname entered earlier when setting up the initial configuration.

After the certificate is generated, you will be prompted for that password later to finish the initial configuration.

Creating the container

I prefer creating containers over just using “docker run”. So that’s the command I’ll be using to create a container for the VPN:

sudo docker create --name [name] \
-v $OVPN_DATA:/etc/openvpn \
-p 1194:1194/udp \
--cap-add=NET_ADMIN \
kylemanna/openvpn

sudo docker start [name]

where [name] is the name for the container – I used “openvpn”. If the container is ever updated, you can just stop and delete the previous container, then re-run the steps above to create a new one. Since it’s operating off a pre-created volume, all your settings and certificates are preserved.

Now to expose it on the firewall. If you’re running Docker on Ubuntu, this step isn’t necessary.

sudo firewall-cmd --zone=[zone] --permanent --add-port=1194/udp
sudo systemctl restart firewalld

where [zone] is the zone for your network adapter.

If restarting the firewall service kicks you off SSH, you’ll need to recreate the OVPN_DATA variable upon next login.

Exposing it in public

In general, when exposing services where they are accessible outside your network, you want to avoid using default port numbers. Either configure the service to use a different port number, or use the port forwarding on the router to provide a different port number.

By default OpenVPN will run on 1194/UDP. And the OpenVPN container will always use that port number. You’ll notice above that all the configuration left this default port in place. I didn’t publish a different port when creating the container.

So securing your exposed VPN service is relatively easy: pick a random port number, preferably north of 32768, and map it to 1194/UDP for the Docker host. The vast majority of hackers will look only for default port numbers.

If your router does not allow this option, then you will need to publish a different port on the Docker host. Instead of -p 1194:1194/udp, use -p [port]:1194/udp, where [port] is a random port number. This also means you’ll need to update the firewall configuration as well to expose the random port number.

Creating client profiles

First, run:

sudo docker run \
-v $OVPN_DATA:/etc/openvpn \
--rm \
-it kylemanna/openvpn easyrsa build-client-full CLIENTNAME nopass

where CLIENTNAME is the name of the profile you’re creating. For example, if I’m creating a profile for my personal cell phone, I’d call it “Kenneth_Phone”, or even “Kenneth_GalaxyS7” since that is the model I have. That way when I upgrade phones, I can create a new profile for the new phone and revoke the profile for my current phone.

With the profile created, now retrieve it. This will save it to the local folder, where you can retrieve it to install it on the client device.

sudo docker run \
-v $OVPN_DATA:/etc/openvpn \
--rm kylemanna/openvpn ovpn_getclient CLIENTNAME > CLIENTNAME.ovpn

Before using the profile on the client device, you will need to edit the file. Look for this line:

remote [host] 1194 udp

If you’re exposing a different port externally for your VPN service, you will need to update the 1194 port number to the port number you’re using.

Backing up your configuration

Now that you have your VPN set up, you likely won’t want to go through that all over again. Especially since it’d require generating new profiles – and certificates – for all your devices. So to avoid that, back up everything in the OpenVPN volume you created earlier.

cd /var/lib/docker/volumes/$OVPN_DATA/_data
tar cvfz ~/openvpn.tar.gz *

Restoring it is straightforward. After recreating the volume, just extract the archive back into the same location.

Conclusions

And that’s about it. The profile you’ve created will work with any OpenVPN client, such as the Android OpenVPN client that I use on my cell phone. Just follow the steps above to create profiles for each device you want accessible from the VPN.

Also remember that security here is paramount. If you believe that any of the client profiles have been compromised, you will want to revoke the certificates for those profiles to prevent them from being used to access your VPN.