OpenVPN is a powerful open-source VPN server that’s capable of encrypting traffic on all supported operating systems. Let’s look at advanced configuration examples to use it to its full potential.
In our previous example, we put together a simple 1:1 client-server infrastructure using a shared key, no routing, and no extra security. You can read more about it here: Create a VPN server with OpenVPN.
We’ll start off from the same basic configuration, where the server config (server.conf) looked like this:
# cat server.conf dev tun ifconfig 10.8.0.1 10.8.0.2 secret static.key cipher AES-256-CBC
And the client config was like this:
$ cat client.conf remote 192.168.247.141 dev tun ifconfig 10.8.0.2 10.8.0.1 secret static.key cipher AES-256-CBC
Let’s serve multiple clients
In its current configuration, the server has a fixed IP of 10.8.0.1 and the single client gets an IP of 10.8.0.2. To create a server with a pool of IP addresses different clients will be assigned to, we need to replace the “ifconfig” line with “server iprange netmask”. Using that, we can define an IP range, the server will get the IP address ending in .1 and clients get all the other IPs in the same network.
In server mode, it’s not possible to use a shared key anymore so we need to come up with another way to authenticate clients. We’ll create certificates for our clients and servers and use those to authenticate. This whole process is detailed in this article: Using client side certificates for secure authentication.
Creating up OpenVPN certificates
To re-iterate the process, we’ll be using the EasyRSA script bundled with OpenVPN to create our certificates. Ideally, this should be done on a separate machine to avoid distributing private keys when they are not needed and only the required files should be copied to the server or distributed to the client.
EasyRSA is downloadable from this location: https://github.com/OpenVPN/easy-rsa/releases.
Starting from scratch, we’ll need to run the following commands to set up the certificate store and create one server and two client certificates:
$ easyrsa init-pki $ easyrsa build-ca $ easyrsa build-server-full server1 nopass $ easyrsa build-client-full client1 $ easyrsa build-client-full client2
The build-ca command will ask for a password that will protect your CA. Every time the build-* commands are run, they ask for the CA password to issue certificates.
The build-server-full command creates the server certificate without password protection, the build-client-full commands create certificates for each client. The password protecting the client certificate will serve as an extra authentication method when connecting. You can also append “nopass” there if you don’t want to add an extra password protecting client connection credentials.
Once this is done, we’re left with the following files to distribute:
- pki/ca.crt – the certificate authority file, the server and the clients will use this to authenticate each other
- pki/private/server1.key – server private key (without password protection)
- pki/issued/server1.crt – server certificate
- pki/issued/client/client1.key and client2.key – client private keys (password protected)
- pki/issued/client1.crt and client2.crt – client certificates
OpenVPN configuration for client/server certificate authentication
On the server side, we should be left with the following files: server.conf, ca.crt, server1.key, server1.crt. We’ll need one extra file (Diffie-Hellman parameters, dh.pem) for secure key exchange, it can be generated by the following command:
openssl dhparam -out dh.pem 2048
Now, all we need to do is to add these files to server configuration so our server config becomes this:
# cat server.conf dev tun server 10.8.0.0 255.255.255.0 cipher AES-256-CBC dh dh.pem ca ca.crt cert server1.crt key server1.key keepalive 10 20
The final “keepalive” parameter makes sure that OpenVPN checks if the connection is alive every 10 seconds and hangs up after 20 seconds of inactivity.
Make sure you always protect your private key files (in this case, server1.key) from read access by chmod 600’ing it to avoid compromising your server security, otherwise random users on your server can read it, decrypt your traffic, implement man-in-the-middle attacks or impersonate your server.
Client side – same thing, we need to generate a dh.pem and copy ca.crt, client1.crt, client1.key to the client then add similar options to its config file:
remote 192.168.247.141 client dev tun tls-client cipher AES-256-CBC cert client1.crt key client1.key ca ca.crt remote-cert-tls server
With this configuration, starting the client will ask for the password of the private key once and then connect securely. The last option (remote-cert-tls) makes sure that the server it’s connecting to is really a server, not another client.
To create another client, simply copy repeat the same procedure but replace client1 with client2 both in filenames when distributing crt/key files and in the config file. You can create as many clients as you want, they can all connect at the same time.
Routing all traffic through OpenVPN
By default, only the traffic directed at the OpenVPN server’s local address (in our case, 10.8.0.1) gets routed through OpenVPN, other connections use the standard internet connectivity of the client.
We can achieve this by setting up the option “redirect-gateway” in the client config. It creates a static route to the VPN server (to avoid losing connectivity to the server itself), then replaces the default route to route all traffic through VPN. “Def1” is a compatibility option that avoids having to completely delete the original default route.
While this option is on the client side, it’s also possible to send it from the server to the client by adding a “push” option to the server. This is an option to push settings to the client side and execute them like they’d be set on the client to simplify centralized management.
Finally, we need to set up the OpenVPN server to NAT traffic and act as a router for clients. VPN clients already route their traffic but the server machine silently ignores it without doing NAT. To do this, we’ll use “iptables” to set up NAT.
We need to enable ip forwarding, by doing this:
echo 1 > /proc/sys/net/ipv4/ip_forward
This can be made persistent and survive reboots by adding this line to /etc/sysctl.conf
Once this is done, we need to run this on the server to set up NAT. It enables source nat (rewriting the source of the packets as they’d be coming from the server itself) for any network packet that comes from the 10.8.0 IP range and not going to 10.8.0 IP range (to avoid doing NAT on local packets). The final IP at the end of the line should be the public facing IP address of the server.
iptables -A POSTROUTING -t nat -j SNAT -s 10.8.0.0/24 \! -d 10.8.0.0/24 --to 192.168.247.141
This is all, now you should have the working internet connectivity through VPN instead of your default internet connection. One thing to pay attention to is DNS – if you have connectivity issues, you may need to change your DNS to a public one that’s accessible through VPN or add the “dhcp-option DNS 22.214.171.124” option to your client config to change the DNS when connected.
Secure services to make them available through VPN only
There are multiple ways to limit access to services through VPN only, either bind them to the VPN’s local address or limit access by using a firewall.
For example, to secure MySQL (port 3306 TCP) and make it available through VPN, add the option
to its config file, where 10.8.0.1 is the VPN local IP address on the server. Unfortunately, some MySQL versions don’t allow multiple IP addresses to be configured in “bind”, this way you either need to bind it to 10.8.0.1 only (this will make the server inaccessible at 127.0.0.1 but sockets will work just fine) or use iptables.
Since OpenVPN may not always be running, we need to tell Linux to allow binding to IP addresses that may not exist yet, otherwise services may fail if OpenVPN is not running. To do this, we need to enable “non-local binds” by appending this line to sysctl.conf:
net.ipv4.ip_nonlocal_bind = 1
Then doing a sysctl -p or running
echo 1 > /proc/sys/net/ipv4/ip_nonlocal_bind
The alternative to all of this is to simply block connections to service from the outside:
iptables -A INPUT -j REJECT -p tcp -i eth+ --dport 3306
This blocks all incoming connections via eth* to port 3306. In newer Linux installations network devices are now called differently, so instead of eth0 and eth1 etc. they are called something like “enp0s1234” so the command above should be modified to include “-i en+” – this will block all “en*” devices.
The same method works for securing any other service, all you need to do is change the port number at the end of the line from 3306 to for example 80 (http), 443 (https), 21 (ftp), and so on.