Building a Simple Household VPN

Last Updated: 2020-02-21

Why Bother?

It seems as though everyone and their dog is currently hawking for VPNs, and we're not going to claim to be any different. Most VPN sales pitches I've seen call focus into two areas: privacy and security. The difference here is that Kensho Security Labs doesn't offer VPN services or even necessarily advise the average person pay a vendor for VPN services. If you have a little bit of technical knowledge and a spare, reliably-up host at your house (such as a raspberry Pi or even a stationary desktop machine), you can spin up a VPN of your own in very short order... with a handful of caveats. In the end, this guide will walk you through setting up a reasonably-secure personal VPN service that uses OTP authentication to keep your traffic reasonably secure.

Security and Privacy Considerations

The set-up described below is not financial-grade corporate VPN-type security, nor is it necessarily appropriate. That's not to say it's not secure per-se; merely that the security provided is more appropriate to a private individual or small group of such individuals. The threat model isn't protecting you from the Mossad or a dedicated attacker - this is to protect against the wide-open nature of coffee-shop WiFi and allow you a sort of "always home" level of convenience and security.

The VPN setup described below will suffice to route your web traffic through the VPN first, adding an extra layer of protection between you and the server. That means, in practicality, that to the outside world you will appear to be the server in all intents and purposes. Traffic you pass from the client machine, over the VPN, is encrypted only until it reaches the VPN server, thereafter transmitting however it normally would from the server to your ultimate destination. This limitation is true of all VPNs.

Finally, while the VPN setup described below obscures the client machine's physical location from whatever service you're ultimately trying to reach, you will still appear to be at whatever location your server is. If you're following the guide as-written and setting up a home system, that means you won't gain any extra evasion advantage for geofencing - which is less of a security concern and more of a entry-level hacking concern, and thus beyond the scope of this guide.

Technical Considerations

The way this guide is written presupposes you have access to a linux host in the debian family, in terms of commands shown. All the software described below is also available to non-debian Linuxes, Windows, and Apple machines, but as the target device is a raspberry Pi or similar, this was deemed sufficient. All of the software described below is free to use and well supported - chiefly, we'll be looking at Docker and OpenVPN.

As always, the commands listed below could have implications going beyond what they show. They're a good place to start learning about the tools we're using, but you're always encouraged to dig as far under the hood as you like.

Step 0:Preparing the VPN Host, Target Network, and Dynamic DNS

As always, we should begin by making sure that you're up to date with the latest package availability. You can run system updates as you normally would - at the very minimum, I would run apt-get update && apt-get upgrade on the target host. The machine you are targeting will become a web-facing host, and keeping it up to date should be a priority.

Additionally, there are two key network steps that need to be taken care of. Firstly, on the target host, you need to open 1194/udp on the firewall. This can be done using the ufw command on many nix systems, or your system firewall of choice, otherwise. Much the same way, you will need to update the configuration of your router to allow inbound traffic from the internet on port 1194/udp to direct to your host. If you haven't already, this is a good time to set up a static DHCP binding for your host as well. Both of those operations are going to be deeply specific to your router, so consult the manufacturer's documentation to ensure you're following the correct process.

Finally, this setup is virtually worthless if you don't have dynamic DNS set up. This is a two-step process - first finding a vendor (like no-ip) to provide a dynamic DNS "hostname" for you, and then installing that vendor's client on the target machine. If for some reason you live somewhere where your ISP assigns you a static IP, then you can skip this process. I'm not aware of too many countries where that statement is true, though.

Step 1: Installing Docker

The core piece of this setup - and its suitability for quick, low-fuss deployment - is the use of the Docker containerization tool to run the VPN server (in our case OpenVPN) inside itself, which avoids a few pitfalls:

  1. Management and maintainance of the OpenVPN install actually belongs to the container maintainers;
  2. Installing the server is faster and less effort-intensive for the average user, and;
  3. Containerizing the service and the storage seperately provides a barrier between the OpenVPN service and the machine itself in the event of compromise.

While docker isn't a bulletproof solution - nothing really is - it's plenty secure for a household VPN such as this and will make our lives a lot easier. To start with, let's install docker itself:

apt-get update && apt-get install docker.io

This command will install most of the docker framework including its dependancies on the machine. While a full discussion of what docker is and what it interfaces to is a little beyond the scope of this conversation, it's still not that bad an idea to read up on it on the Docker website.

As an additional note: in general it is considered good practice not to actually run operations in docker as root. To get around that, you can add yourself to the docker group with command sudo usermod -a -G docker $USER. The next time you log in after that command is issued, you will be added to the docker user group, which will give you permission to use docker commands without needing to first elevate to root - also meaning that they execute as you rather than root. You can validate the setting took affect after re-logging in with the command groups.

Step 2: Setting up the OpenVPN Volume and Container

At heart, Docker Containers are ultimately ephemeral; that's sort of the point. They have no real memory on their own. Fortunately, docker provides a capability it calls "volumes", which provide mountpoints between your actual filesystem and the filesystem inside the container. We're going to use one of those volumes to hold all the configuration information needed for OpenVPN. This sounds complicated, but the bulk of the work is already done for us by Kyle Manna, who prepared a docker image of OpenVPN along with a number of nice utilities: see the main github here.

Note: Please note that this is one of the points of insecurity for the project. We'll discuss why this is in detail as we reach the relevant points, but an important note is that anyone who can gain root inside the container can impersonate the VPN and trick you into connecting to it, as it's currently set up. However, we're taking some mitigating steps to prevent this, and such an attack isn't really necessary to be concerned with for the average person with an average threat model.

To begin with, we're going to set an environment variable that names this service. It doesn't really matter what you pick, but only change the "example" portion of the name - the rest is used to control a service table entry via systemd. We also want to set up another environment variable to hold the hostname we can reach the server at - your DynDNS hostname is fine, if you don't have a proper DNS registration for the host.

OVPN_DATA="openvpn-data-example"
OVPN_HOSTNAME="udp://your.host.name"

The next three commands begin the installation process by creating a volume to hold the configuration data, running a tool in a docker container attached to that volume to generate OpenVPN's configuration files, and then running an interactive container attached to the same volume to handle setting up the Public Key Infrastructure tooling for your VPN. The log-driver command prevents that sensitive information from being dropped into the host machine's logs, which would be a security risk otherwise. We also set up a few slightly-better-than-default settings in preparation for using OTP Authentication.

docker volume create --name $OVPN_DATA

docker run -v $OVPN_DATA:/etc/openvpn --log-driver=none --rm kylemanna/openvpn ovpn_genconfig -u $OVPN_HOSTNAME -2 -c SHA-256-CBC

docker run -v $OVPN_DATA:/etc/openvpn --log-driver=none --rm -it kylemanna/openvpn ovpn_initpki

The second command will prompt you to create a password for the root certificate. You can think of this as a sort of master password for the whole VPN system, and should keep a record of it in your password manager. You will need it, among other things, for adding or readding users to the system.

Using the PKI in this way, while well within the safety limits of the average person, is a strong example of having favoured convenience over security. The single biggest change you could make to this design is to initiate the PKI on a seperate system and only feed the server's own keys into it - having unauthorized access to the PKI would allow a malicious entity to impersonate the VPN or forge unauthorized access packages. However, this kind of attack is not in the average person's threat model and segregating the PKI involves much more maintainance for the operator - I therefore leave it as an exercise to the reader. The general method is discussed here.

The other thing we need to do is to create one or more client certificates. These client certs are fundamentally how you will connect to the VPN, along with anyone else who needs to do so. For obvious reasons and as usual, we don't recommend sharing those credentials among multiple physical users. Also, this method generates things slightly differently than you would expect for the image defaults as we are attempting to set things up to use OTP.

One-time Password Authentication is preferred for VPN connections for a small handful of reasons. For starters, it avoids the VPN admin having knowledge of the passwords of all users (since it's the admin who has to create these packages). It also allows the client cert to be password protected (after a fashion) in case the package is compromised or leaked. Instead of a password protecting the certificate itself, the OpenVPN server can issue the user a challenge - prove you have access to a secret we (the user and the server) shared previously, and can calculate a One-Time Password as a result. Kyle's OpenVPN docker container does this by leveraging Google Authenticator.

If you're physically accessing the host, this has an additional advantage - the QR code to set up Google Authenticator will be displayed in the terminal, allowing you to scan it immediately rather than transferring it to the user via email or some other potentially-insecure method, assuming your user has access to you. This first command generates the user account. The new user is valid only for containers sharing $OVPN_DATA; they aren't now users on the host system itself. We do so without assigning a password - you can add a password as well (by omitting nopass) if you want to use the OTP as a form of two-factor authentication.

docker run -v $OVPN_DATA:/etc/openvpn --log-driver=none --rm -it kylemanna/openvpn easyrsa build-client-full $some_username nopass

This command is run in interactive terminal mode (-it) because it will prompt you, among other things, for the PKI password you created previously. This is necessary because the client certificate is also signed by the PKI, so that the server gets similar proof-of-identity about the client to what the client gets about the server. This is Transport Layer Security (TLS) - the same technology as HTTPS, though most HTTPS does not validate the client.

Next, we need to create the actual One Time Password configuration for your user.

docker run -v $OVPN_DATA:/etc/openvpn --log-driver=none --rm -it kylemanna/openvpn ovpn_otp_user $some_username

This is the step when the authorizing QR code is generated - if you don't have the user on hand, a link to the QR code in google graphs is also generated. The QR code (or the link to it) is as good as a key and should be treated accordingly.

Finally, this command will spit out a file with all the relevant connection information for your user. The user needs a copy of this file, as it is what they put into the OpenVPN client on their machine in order to connect to the VPN down the road.

docker run -v $OVPN_DATA:/etc/openvpn --log-driver=none --rm kylemanna/openvpn ovpn_otp_user ovpn_getclient $some_username > $some_username.ovpn

Once you have your users set up and your certificate files ready to go, the last thing left to do is to set up persistance for the OpenVPN container as though it was a service. While we've been rather blunt about our reservations for systemd in the past, this project relies upon it for this purpose - you'll have access to it anyway on a debian-based system like a raspberry pi or similar light server.

Essentially, there's two operations needed to do this. The first is to drop the actual service record from the github repo into services, which requires you to have the ability to sudo and uses the following command:

curl -L https://raw.githubusercontent.com/kylemanna/docker-openvpn/master/init/docker-openvpn%40.service | sudo tee /etc/systemd/system/docker-openvpn@.service

This pulls down a copy of the service record, both printing it to the terminal and saving it in the appropriate location to control it with systemd's systemctl utility, replacing example with whatever value you replaced it above for $OVPN_DATA:

systemctl enable --now docker-openvpn@example.service

systemctl status docker-openvpn@example.service

The second command shows the status of the service, which should be Active - press q to get out of that process unless you really want to hold a terminal window open to monitor it. At this stage, the service is up and configured - though as you've surely guessed, we need to do some work on the client machines to be able to connect it, and of course, we'll want to test it out once we're done.

Step 2: Configuring Clients

There's not much that needs to be done. Of course, the user needs the username you set up for them as well as the .ovpn file that you created toward the end of the previous step, but they also need a client. If your client machine is also on linux and uses apt, you can apt-get openvpn in order to install a simple command line client, which you then invoke by calling sudo openvpn some_key_file.ovpn. On other OSes, OpenVPN make a client available for download.

Regardless of how you connect in terms of client, you will be prompted for a username (the username you defined when generating the user credentials) as well as an auth password, which is actually the code pulled from Google Authenticator.

Step 3: Validating Your Configuration

The simplest test method is as follows, and can be conducted from home, as long as your have the ability to get onto an alternate network such as by mobile tethering. Otherwise, running the test from any public network would suffice.

  1. Connect the client machine to the network without connectiong to the VPN, and obtain your public IP.
  2. Connect to the VPN, and obtain your public IP: it should have changed.
  3. Collect the public domain for your VPN's hostname/dns address; you can do this with dig, for example.
As long as the second and last IPs match, you've validated that at least your web traffic is passing through the VPN - stricter validation is left largely as an exercise to the reader.


The research, development, and ongoing support of Kensho Security Labs projects are ventures in providing free services. If you'd like to support the lab in making sure that packages like Tapestry, or guides like this one, remain free to use for anyone who needs them, consider making a donation or leaving a suggestion using the link on our support page.