A complete Pi based VPN Server for under £20 (Buster)
Updated 9th November 2019 for Raspbian Buster
This article describes how, starting from scratch, to build a complete VPN server based on a Pi Zero for under £20. The method will also work for other Pi models.
There are many tutorials covering this but some are out of date (pre-Buster) and some assume a fair amount of knowledge about what is going on and may be tricky for the uninitiated to follow. I have tried to write this assuming minimum experience on the part of the reader and have concentrated on what to do rather than explaining the concepts of VPN, keys, certificates and so on. To get more background understanding the excellent tutorial by Lauren Orsini at readwrite.com is well worth a read and I learned much from it.
Before you start, check whether your router can operate as a VPN server. Many modern modems can do that and if yours can then it is likely that it would be better to use that rather than use a Pi.
Pre-requisites
- A Raspberry Pi of any flavour, with a network connection (wifi or ethernet).
- For initial setup either a wired ethernet connection or a display and keyboard. To use a wired ethernet connection on a Pi Zero you will need a USB Ethernet adaptor.
- A PSU.
- An SD card with up to date with Raspbian Buster Lite installed, you can download Raspbian Lite from here and load it onto the card as described in the linked installation guide. In fact it should be ok with the Desktop version of Raspbian installed but why would you do that?
- A PC on the network to connect to the Pi with an ssh client. This is available by default on most Linux operating systems. For Windows you may need to install PuTTY, though I see that Windows 10 may now have an ssh client built in.
- You might want some sort of case to put it in when it is complete
First Steps, Setup the router
In order to access the VPN from the internet there are two requirements. Firstly the IP address of the router must be known. Your internet connection may have a fixed name or ip address but more usually a service such as duckdns.org is used to get a fixed name. How you set this up will depend on which service you go for. For the examples here I have assumed that the host name is my_host_name.duckdns.org.
Secondly you must setup port forwarding for UDP and TCP in your router so that port 1194 is forwarded to the pi. How you do that is dependent on the router.
Install Raspbian on the Pi
We want the Pi configured as a headless server, programmed and controlled from a PC. To do this follow the instructions in the article Configure your Pi as a Headless Server. You need to give the Pi a fixed IP address as described in that post.
You should now have a Pi running raspbian that you can login to from your PC
Install packages
The packages openvpn and easy-rsa need to be installed, so connect to the pi and run
sudo apt install openvpn easy-rsa
Build the server certificates and keys
Run the following commands to create the easy-rsa directory and copy the sample files into it
sudo mkdir /etc/openvpn/easy-rsa
sudo cp /usr/share/easy-rsa/* /etc/openvpn/easy-rsa
Copy the easy-rsa example vars file
sudo cp /etc/openvpn/easy-rsa/vars.example /etc/openvpn/easy-rsa/vars
The default certificate expiry time is ten years for the CA certificate but only three for the others. If you want to change either of those then edit the copied file /etc/openvpn/easy-rsa/vars
sudo nano /etc/openvpn/easy-rsa/vars
find the lines setting defining those settings (which are in days)
# In how many days should the root CA key expire?
#set_var EASYRSA_CA_EXPIRE 3650
# In how many days should certificates expire?
#set_var EASYRSA_CERT_EXPIRE 1080
then to change either of them remove the ‘#’ characters to uncomment the appropriate line and set the values as you desire and use Ctrl+S to save it and Ctrl+X to exit. So for example to set the certifacte expiry period to 10 years change that line to
set_var EASYRSA_CERT_EXPIRE 3650
Prepare to build the certificate and keys
Create an empty directory structure for the keys, /etc/openvpn/easy-rsa/pki, using the easyrsa command.
cd /etc/openvpn/easy-rsa/
sudo ./easyrsa init-pki
Build the certificates and keys
Leave all settings at default values when prompted for data below, except to say yes when
asked about signing the certificate. gen-dh
in particular may take a very long time, particularly on a low
powered pi, several cups of coffee and an afternoon nap may well be in order.
sudo ./easyrsa build-ca
sudo ./easyrsa gen-req server nopass # accept the default [server] when prompted
sudo ./easyrsa sign-req server server
sudo ./easyrsa gen-dh
sudo openvpn --genkey --secret ta.key
copy ta.key from /etc/openvpn/easy-rsa to /etc/openvpn/
copy ca.crt dh.pem from /etc/openvpn/easy-rsa/pki to /etc/openvpn/
copy server.crt from /etc/openvpn/easy-rsa/pki/issued to /etc/openvpn/
copy server.key from /etc/openvpn/easy-rsa/pki/private to /etc/openvpn/
sudo cp ta.key /etc/openvpn/
sudo cp pki/{ca.crt,dh.pem} /etc/openvpn/
sudo cp pki/issued/server.crt /etc/openvpn/
sudo cp pki/private/server.key /etc/openvpn/
exit
Configure OpenVPN
Reboot as a check that all is well so far. Assuming it is then edit /etc/sysctl.conf
using
sudo nano /etc/sysctl.conf
Find the line
#net.ipv4.ip_forward=1
remove the # at the start (to un-comment that line), save the file and exit.
To prepare the server configuration file etc/openvpn/service.conf
we start with the one from
/usr/share/doc/openvpn/examples/sample-config-files/server.conf.gz
as follows
sudo -s
gunzip -c /usr/share/doc/openvpn/examples/sample-config-files/server.conf.gz > /etc/openvpn/service.conf
exit
sudo nano /etc/openvpn/service.conf
Find the line
dh dh2048.pem
and change it to
dh dh.pem
Uncomment, by removing the # or ; if present at the front, from these lines (which are not together in the file)
topology subnet
comp-lzo
push "redirect-gateway def1 bypass-dhcp"
tls-auth ta.key 0
user nobody
group nogroup
mute 20
In addition find the two lines starting ;push dhcp-option DNS ....
, remove the semi-colons and (if you want)
change the ip addresses to the DNS servers of your choice.
Some public wifi access points block port 1194, which is the standard VPN port. If you would like to change that then find the line
port 1194
in that file and change it to the port you prefer. If you do that you will also have to forward that port in the router instead of 1194 and if you use the ovpn_generate script at the end of the blog you will have to change it in the script too.
Now we can start the server, it is necessary to start two services
sudo systemctl restart openvpn
sudo systemctl restart openvpn@service
Run tail -n 50 /var/log/syslog
which will print the last 50 lines of the log and you should
see a page full of ovpn-server messages. Provided there is nothing there that suggests it is not
starting up then run
ifconfig
and you should see an extra network interface, tun0
with the inet address 10.8.0.1
If it doesn’t work but the logs do not contain anything helpful then try a reboot and start openvpn again. The log /var/log/daemon.log may have useful information also.
To setup openvpn so that it automatically starts on boot, run
sudo systemctl enable openvpn
sudo systemctl enable openvpn@service
Configure iptables
For the vpn to work, iptables must be configured appropriately to forward data to the correct destinations. I prefer to keep iptables configuration in `/etc/rc.local’ which is executed on startup. To do it this way edit the file
sudo nano /etc/rc.local
and this just before the exit 0
at the end. Note the last lines below, choose either the eth0 or wlan0 line
# flush current iptable rules so this can be run at any time to restore rules
# though note that this may clear rules set elsewhere
sudo iptables -F
sudo iptables -t nat -F
sudo iptables -X
# for vpn
iptables -A FORWARD -m state --state RELATED,ESTABLISHED -j ACCEPT
iptables -A FORWARD -s 10.8.0.0/24 -j ACCEPT
iptables -A FORWARD -j REJECT
# if using wired ethernet then include this line
iptables -t nat -A POSTROUTING -s 10.8.0.0/24 -o eth0 -j MASQUERADE
# if using wifi then comment out above and uncomment this one
#iptables -t nat -A POSTROUTING -s 10.8.0.0/24 -o wlan0 -j MASQUERADE
To invoke this manually, in a terminal run
sudo /etc/rc.local
Make the keys and certificates for the clients
For each client device that you want to be able to connect to the VPN (laptop, phone etc) you need to make a set of keys and certificates. You have to give each of the sets a name. In this example the server is called owl and the client tigger. I am using the name tigger_owl so I can remember that these are for tigger to connect to owl
cd /etc/openvpn/easy-rsa
sudo ./easyrsa gen-req tigger_owl
This will ask for a PEM pass phrase that the user will have to provide when connecting to the VPN. If nopass
is added to the gen-req command line then it will not be necessary for the user to provide the password.
It is generally not a good idea to use nopass
, as that would mean that a malicious user getting hold of the client laptop
or mobile device may be able to access your local network.
Next run the command (replacing tigger_owl with your chosen key name obviously)
sudo ./easyrsa sign-req client tigger_owl
The command will ask you to provide the pass phrase that you entered when creating the CA certificate in the
section “Build the certificates and keys” earlier.
This makes, in the pki/issued subdirectory, tigger_owl.crt
that
the client will need to connect to the VPN. The client will also need the server key and certificate
ca.crt
and ta.key
that the earlier process created and that we copied to /etc/openvpn.
Many client applications can be given a combined ovpn configuration file rather than the individual files. In order to construct an ovpn file create a file called ovpn_generate.sh in /etc/openvpn/easy-rsa containing the script that can be found a the end of this blog. This can be done using
cd /etc/openvpn/easy-rsa
sudo nano ovpn_generate.sh
and pasting the script into that (use Ctrl+Shift+V to copy into nano which is a terminal application). Then save it and exit from nano. To make it executable run
sudo chmod +x ovpn_generate.sh
Then to create the ovpn file for the example above where the client is called tigger and the server owl, if the domain name of the vpn were, for example, my_host_name.duckdns.org, run
cd /etc/openvpn/easy-rsa
sudo ./ovpn_generate.sh tigger_owl my_host_name.duckdns.org
Repeat the gen-req and sign-req commands (and ovpn_generate if required) for each client.
Testing
Having configured an openvpn client on a PC or device, login to the pi from a local PC (preferably not the one about to be used to connect to the VPN,but if you have not got another device then it will probably be ok) and run
tail -f /var/log/syslog
Then connect from the client device. The log should show the sequence of connection. If the client cannot connect then there may be some useful messages there.
That’s it. All done.
Performance
I was concerned as to whether a Pi Zero had the horsepower to provide a useful VPN, however my tests gave a throughput rate of 10Mbps which is plenty for my purposes. My Pi uses a Wifi link and my Wifi hub is old and a bit flaky so it may well be that this limit was imposed by the Wifi and not by the Pi. All the data has to be received by the Pi and then re-transmitted so 10Mbps throught the server implies 20Mbps across the Wifi.
Script for ovpn_generate.sh
#!/bin/bash
##
## Usage: sudo ./ovpn_generate.sh key_name server
## e.g. sudo ./ovpn_generate tigger_owl myhostname.duckdns.org
##
# Given a name used to identify the key and a server host name or ip address
# it builds the file /etc/openvpn/easy-rsa/pki/key_name.ovpn
# Run with sudo after generating keys and certificates
# see http://blog.clanlaw.org.uk/pi-vpn-server.html for more details of usage
if [ "$EUID" -ne 0 ]
then echo "Must be run with sudo"
exit
fi
key_name=${1?"The key name is required"}
server=${2?"The server host name or ip address is required"}
cd /etc/openvpn/easy-rsa
cacert="pki/ca.crt"
client_cert="pki/issued/${key_name}.crt"
client_key="pki/private/${key_name}.key"
tls_key="ta.key"
out_file="pki/${key_name}.ovpn"
if [ ! -e "${cacert}" ]
then
echo "File ${cacert} not found"
exit
fi
if [ ! -e "${client_cert}" ]
then
echo "File ${client_cert} not found"
exit
fi
if [ ! -e "${client_key}" ]
then
echo "File ${client_key} not found"
exit
fi
if [ ! -e "${tls_key}" ]
then
echo "File ${tls_key} not found"
exit
fi
echo "Building ${out_file} for ${server} from ${tls_key}, ${cacert}, ${client_cert} and ${client_key}"
cat > ${out_file} << EOF
client
dev tun
remote ${server}
nobind
persist-key
persist-tun
verb 1
port 1194
proto udp
cipher AES-256-CBC
comp-lzo
remote-cert-tls server
key-direction 1
<ca>
EOF
cat >> ${out_file} ${cacert}
cat >> ${out_file} << EOF
</ca>
<cert>
EOF
cat >> ${out_file} ${client_cert}
cat >> ${out_file} << EOF
</cert>
<key>
EOF
cat >> ${out_file} ${client_key}
cat >> ${out_file} << EOF
</key>
<tls-auth>
EOF
cat >> ${out_file} ${tls_key}
cat >> ${out_file} << EOF
</tls-auth>
EOF