This post is part of my Explaining My Configs series where I explain the configuration files (and options) I use in detail.

This post could either be read as a whole, or as a reference (click on a line to jump to its explanation).

Usually I prefer explaining the client and the server configurations in separate posts, however, with OpenVPN, there is significant overlap between the two, and they need to match in order for it to work. Therefore, this post will explain both.

I have my configuration files in the system's OpenVpn directory ( /etc/openvpn/server for me), and for each configuration file, I have a directory with its keys, so for example, this is the layout for the server's config:

server.conf server/dh.pem server/ipp server/ta.key server/key.key server/cert.crt server/ca.crt

What is this config for?

I use my VPN to have remote access into my network, and sometimes also to route all traffic through it, in order to escape some forms of connection filtering. While my base configuration is hardened (strong encryption and secure settings), my route-all-traffic configuration is not. That requires some settings that are annoying to use and setting up a firewall to block mistakes.

The config file

Click on a line to jump to its explanation.

/etc/openvpn/server.conf:

/etc/openvpn/client.conf:

Reviewing the config

server 192.168.87.0 255.255.255.0

The server puts OpenVPN in server mode, and supplies it with a subnet of IPs to allocate by specifying an address and a netmask. In the example above, OpenVPN will take 192.186.87.1 for itself, and allocate the rest of the subnet for clients.

Choose a subnet that's unlikely to create clashes with your other networks.

port 1194

This directive sets which port the server should listen on. I chose the standard OpenVPN port.

proto udp6

This sets the transport protocol to use. UDP is more efficient and will perform much better while using less data. However, if for some reason you can't use UDP, you can set this to TCP instead.

On the server I set it to udp6 which tells it to listen to both IPv4 and IPv6 connections.

On the client I set it to udp , because udp6 will force it to only try IPv6, and made OpenVPN not work for me (I don't always have IPv6).

dev tun0

Sets the name of the virtual network device to use. On the server, I forced it to tun0 , so I can more easily set firewall rules knowing it'll always be the same device. On the client I let it choose the exact device on its own.

Since the device name starts with tun , OpenVPN automatically sets the device type to it. Otherwise I would have had to set dev-type explicitly.

client-to-client

Normally, OpenVPN would pass all packets to the tun device on the server. This means that all packets, even between clients in the VPN network will be handled by the server's firewall, so if you want client to client traffic, you need to explicitly enable it in the firewall and add all the rules to do it.

Alternatively, you can set this directive that automatically does all of that for you. Be advised that with this enabled, the server's firewall will never get the packets, so don't enable it if you want fine-grained control.

cipher AES-256-CBC auth SHA512 tls-cipher TLS-DHE-RSA-WITH-AES-256-GCM-SHA384:TLS-DHE-RSA-WITH-AES-256-CBC-SHA256:TLS-DHE-RSA-WITH-AES-128-GCM-SHA256:TLS-DHE-RSA-WITH-AES-128-CBC-SHA256

These statements harden the server with stronger crypto. However, some of them are only available from OpenVpn 2.3.3. If you are having trouble, choose different values.

The cipher directive controls which cipher would be used for the data channel, that is, all the data transferred through the VPN.

The auth directive controls the HMAC algorithm used for the control channel. More on that in the keys section.

The tls-cipher directive controls the cipher suite used by the VPNs control channel.

comp-lzo no

Disable compression. Most, if not all, of my traffic is either encrypted (e.g. SSH and HTTPS) or already compressed (e.g. HTTP in most cases), so adding compression on top will not add any benefits. If you think you may want compression after all, set it to adaptive which automatically decides if to put compression on or not, though even when not compressing, this is not free.

keepalive 15 60

This is a helper directive that automatically sets the ping and ping-restart values on both the client and the server (when set on the server). This is why we don't have it in the client.

With this setting, a ping will be sent every 15 seconds (if no other data has been sent). This is useful to keep stateful firewalls will not drop or UDP connection after some time of inactivity.

The second part of this directive will try to restart the connection after 60 seconds of inactivity (on the client) and 60 * 2 = 120 seconds (on the server). This ensures that the client will detect the timeout before the server.

Note: be aware, that this setting can cause increased battery usage on mobile devices due to the radio being woken up to send data all the time.

ping-timer-rem

This only runs the ping-restart (previous) timeout timer when there is a client connected to the server, so to prevent the server from timing out all the time when there isn't even a client connected.

ifconfig-pool-persist server/ipp

This makes the IP addresses given to clients persistent, and the persistence file to be saved in the location server/ipp relative to the main config file.

verb 3

Increase the verbosity of OpenVPN. The default is 1, but 3 is the recommended value. Shows a bit more information in the logs.

persist-tun persist-key

These options persist the tun device and the authentication keys across restarts (either caused by user or ping-restarts). This makes it possible to drop privileges (see the next section) and regardless, I couldn't think of a good reason why not to have these.

user nobody group nobody

This drop the privileges of the daemon to the user and group nobody , so if there's ever a bug in OpenVPN, at least the hacker won't get root privileges. You could further harden it as explained on the OpenVPN website, however, it was too much of a hassle for not a lot of benefit.

Because of the lower privileges, the server can no longer clean up after itself by for example closing the tun device, or removing routes. It's a non-issue for a server config because OpenVPN should never be stopped, but it is for a client, and that's why I only have this setting on the client machine.

tls-auth server/ta.key 0 cert server/cert.crt key server/key.key ca server/ca.crt dh server/dh.pem

These tell OpenVPN to look for the keys (and dh params) in the noted locations. Please note that the number at the end of tls-auth is the key-direction , and needs to be 0 for server and 1 for client.

You can also embed the keys/certificates in the file itself if you prefer (demonstrated in the next section), and if you do it for the tls-auth directive too, you'd need to specify the key-direction separately using the key-direction directive.

Please look online to see how to manage your own PKI for OpenVPN, this is beyond the scope of this post. However, one note on tls-auth as promised earlier. This is the key used for HMAC. While this is not essential, it adds another layer of security by adding an easy to verify authentication code to all the control messages. This protects against some kinds of DoS and could even protect against issues with TLS, like for example, the recently notorious heartbleed .

< ca > -----BEGIN CERTIFICATE----- ... SNIP ... -----END CERTIFICATE----- </ ca >

I redacted my actual certificate for brevity, but this is how you embed a key or certificate in the config file. I found it very useful when providing the OpenVPN Android client with a config. When I did that, I embedded all of the keys.

client

This is a helper indicating that this is a client. This means that the client assumes the position of "client" in the TLS negotiation. It also sets the pull directive, which allows the server to push some (white-listed) configurations to the client like setting the IP address and routing.

remote vpn.stosb.com 1194 udp

This tells OpenVPN where to connect to. In this example it's connecting to vpn.stosb.com and port 1194 using UDP, which is what we have set on the server.

You can put multiple remote directives for redundancy. For example, you could have OpenVPN running on a list of ports you expect to be white-listed in networks, like udp:53 (DNS) or tcp:993 (POP3) and then have OpenVPN automatically try them. Alternatively you can use it for real redundancy, so if one server fails, the others are automatically tried.

This directive redirects all of the client's traffic through the VPN by adding the needed routing rules. This is the bare minimum that's needed, but you could also append other flags to this command, for example block-local to block traffic to the local (non-VPN) LAN in order to make sure no traffic leaks. This is another level of hardening, but as I said before, it's better handled by proper firewall rules.

One thing to remember when using block-local , is that in many cases your DNS server would be in the local network and with block-local it will no longer be reachable. To solve it you should either set an alternative DNS in the client, by using for example dhcp-option DNS 8.8.8.8 , or pushing it from the server.

remote-cert-tls server

This forces the certificate of the other end to be a server certificate. Before explaining what it does, I need to explain a bit about PKI and OpenVPN. When a client connects to the server using TLS, it checks to see if the certificate of the server is signed by the certificate authority and not revoked, and if everything is OK, it allows the connection. This sounds good, but because of how OpenVPN works client certificates are also signed by the certificate authority, so a client could potentially impersonate the server, which is unacceptable.

One way to solve it, is to use the tls-verify directive. This directive lets you run a command to verify the server is who it says it is by checking the public key (public key pinning), CN and whatever else you may feel like. However, t's annoying to set-up because you need to create and deploy and extra script.

Another way to solve it, which is less secure but much simple, and can be used in addition to the first method is to mark server certificates as such and verify it in the client. When creating the server certificate you just need to mark it as a server certificate, and then in the client add the remote-cert-tls server directive to enforce that. I find this solution sufficient, and it's what I use.

Please let me know if you spotted any mistakes or have any suggestions, and follow me on Twitter or RSS for updates.