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:
server 192.168.87.0 255.255.255.0 port 1194 proto udp6 dev tun0 client-to-client 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 comp-lzo no keepalive 15 60 ping-timer-rem ifconfig-pool-persist server/ipp verb 3 persist-tun persist-key # Drop privs user nobody group nobody # Keys tls-auth server/ta.key 0 cert server/cert.crt key server/key.key ca server/ca.crt dh server/dh.pem
/etc/openvpn/client.conf:
client remote vpn.stosb.com 1194 udp dev tun # Uncomment the next line to redirect all traffic through the VPN # redirect-gateway def1 remote-cert-tls server 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 comp-lzo no verb 3 persist-tun persist-key # Keys key-direction 1 tls-auth client/ta.key cert client/cert.crt key client/key.key <ca> -----BEGIN CERTIFICATE----- ... SNIP ... -----END CERTIFICATE----- </ca>
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.
# Drop privs 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.
# Keys 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.
# Uncomment the next line to redirect all traffic through the VPN # redirect-gateway def1
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.