/ linux security

Comprehensive OpenVPN Tutorial

In this tutorial, we will look at managing an openvpn server and certificate authority and will provide a detailed breakdown of the configuration options

Table of contents

  1. Setting up a CA
  2. Server Preparation
  3. Client preparation
  4. Signing requests on the CA
  5. OpenVPN configuration
  6. Example config
  7. A breakdown of the config directives
  8. Systemd unit
  9. Redirect DNS traffic

The Certificate Authority

We will be setting up our certificates using a utility known as easy-rsa which is a wrapper for OpenSSL. For security reasons, we will have the CA on a seperate machine .

We have easy rsa in three separate environments.
1. CA
2. The server
3. The client

The CA is the most important component of the authentication infrastructure. As a simple analogy, think of the certificates as passports and the client and server as a passenger and officer; the CA is the government issuing the passports. In fact, most modern electronic passports contain cryptographic information signed by the manafacturer's CA.

If you don't want to use a separate machine for your CA, you can just build the CA in a different folder. Once your server is up and running, it's highly recommended you move it somewhere offline

Build the CA environment

root@CA # ca_path=/root/identity/vpn_ca
root@CA # mkdir --parent $ca_path && cd ca_path
root@CA # cp -/usr/share/easy-rsa/3.0.1/* .

# If you wish to change days till expiry etc 
root@CA # nano easyrsa 

Now we can generate the CA cert and key

The key is protected with a passphrase. It doesn't have to be a crazy complex random string. The critical part is making sure its completely confidential;

[root@CA]# ./easyrsa build-ca
Generating a 2048 bit RSA private key                                                                                                                                                       
writing new private key to '/root/identity/vpn_ca/ca.key.xxx'                                                                                                                    
Enter PEM pass phrase:                                                                                                                                                                      
Verifying - Enter PEM pass phrase: 
A creation complete and you may now import and sign cert requests.                                                                                                                         
Your new CA certificate file for publishing is at:

The CA.crt file is required on both the server and all clients

Generating a certificate revocation cert

[root@CA ]# ./easyrsa gen-crl
Using configuration from /root/identity/vpn_ca/pki/openssl-1.0.cnf
Enter pass phrase for /root/identity/vpn_ca/pki/pki/private/ca.key:

An updated CRL has been created.
CRL file: /root/identity/vpn_ca/pki/crl.pem

Transfer this file to the server

Preparing the Server

Preparing the environment

[root@SERVER ]# cp /usr/share/easy-rsa/3.0.1/* /etc/openvpn/server
[root@SERVER ]# cd /etc/openvpn/server && ./easyrsa init-pki
[root@SERVER ]# mkdir /etc/openvpn/server/ccd

Generating Diffie-Hellman Parameters

Diffie-Hellman is used to ensure forward secrecy.
I explain what it works and how it works here (it's very awesome algorithm)

[root@SERVER ]# openssl dhparam --out dh.pem 4096  
Generating DH parameters, 4096 bit long safe prime, generator 2
This is going to take a long time

Generating the certificate signing request for the server

We begin by generating a certificate signing request (CSR) for the server
This CSR will be sent to our certificate authority. We then log into the CA and sign the request (requires the CA key passphrase).

Before attempting to establish a VPN connection, clients need to be confident that the server they have connected to is actually the real deal. We only accept a server if its presents a certificate that has been signed by the CA .

This is why we use a private internal CA because (unless your passphrase is compromised) anything signed by the CA can be assumed to be trusted because you were the one who signed it. As opposed to a commercial third party CA signing thousands or millions of certs.

[root@SERVER ]# ./easyrsa gen-req vpn-example.etherarp.net

We then copy the CSR to our CA; we will use scp/ssh to do it over the network

[root@SERVER ]# scp pki/reqs/vpn-example.etherarp.net.req user@CA:/tmp

If you're really paranoid, compare the hashes side by-side by running openssl sha512

The gen-req command creates a certificate signing request and a key. The key is by default encrypted. For client side this is a good security benefit, you don't want whoever finds your lost usb stick to access your network.

However, on server side, there are disadvantages. Each time the OpenVPN service is restarted, you will need to manually enter in the passphrase, so its less than ideal.

We will remove the passphrase on the servers key

[root@SERVER]# openssl rsa -in vpn-example.etherarp.net.key -out /etc/openvpn/server.key
Enter pass phrase for vpn-example.etherarp.net.key:
writing RSA key

Setting up the TLS Authentication static key (ta.key)

[root@SERVER ]# openvpn --gen-key --secret ta.key

Generating a template for clients

Let's make a template for the client configuration file. The only difference between clients is that every client has its own cert and key.

[root@SERVER ]# cat > template
remote vpn-example.etherarp.net 1194
remote-cert-eku "TLS Web Server Authentication"
cipher AES-256-CBC  
dev tun  
proto udp  
script-security 2
auth SHA512  
keepalive 10 120  

All clients must possess the ta.key and ca.crt

We can include files in-line in the client configuration file. So lets append our template to include them.

echo "<ca>" >>template && cat ca.crt> >template && echo -e "</ca>\n<tls-auth>">>template && cat ta.key >> template && echo -e "</tls-auth>\nkey-direction 1" >> template

We can generate this template on the server

Iptables Firewall
Assuming your vpn subnet is ""
and your internet interface is named eth0 and has an address of

[root@SERVER ]# iptables -t nat -A POSTROUTING -s $VPN_SUBNET -o eth0 -j SNAT --to-source $PUBLIC_IP
[root@SERVER ]# iptables -I INPUT -p udp --dport 1194 -j ACCEPT
[root@SERVER ]# iptables -A FORWARD -i tun0 -o eth0 -j ACCEPT
[root@SERVER ]# iptables -A FORWARD -i eth0 -o tun0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
[root@SERVER ]# iptables -A FORWARD -j REJECT --reject-with icmp-port-unreachable

Client preparation

Building the client environment

[root@CLIENT ]# client_path=/root/openvpn-client
[root@CLIENT ]# mkdir $client-path
[root@CLIENT ]# cp /usr/share/easy-rsa/3.0.1/* $client_path
[root@CLIENT ]# cd $client_path && ./easyrsa init-pki

Generating the certificate signing request

[root@CLIENT ]# ./easyrsa gen-req desktop
[root@CLIENT ]# scp pki/reqs/desktop.req user@CA:/tmp

Signing certificate requests

Now we head back to our CA, and make sure the requests from the server and client have been transferred.

Importing the request

[root@CA ]# ./easyrsa import-req /tmp/vpn-example.etherarp.net.req vpn-example

The request has been successfully imported with a short name of: vpn-example
You may now use this name to perform signing operations on this request.

Signing the requests
(I've cleaned the output a little bit).
We sign the requests, making sure to first specify whether it's a client or server. We use the short name we imported it under.

Remember: We need to sign the request for both the client and the server

After entering your CA keys passphrase, you should be good to go.

[root@CA]# ./easyrsa sign-req server vpn-example.etherarp.net 

We now need to import and sign our client request

[root@CA]# ./import-req /tmp/desktop.vpn-example.etherarp.net.req desktop
[root@CA]# ./easyrsa sign-req client desktop

You are about to sign the following certificate.

    commonName                = desktop.vpn-example.etherarp.net

Confirm request details: yes
Enter pass phrase for private/ca.key:
Signature ok

The Subject's Distinguished Name is as follows
commonName            :ASN.1 12:'desktop'

Certificate is to be certified until Nov 15 09:27:25 2018 GMT (365 days)
Certificate created at: pki/issued/desktop.crt

Now all you've got left to do is copy desktop.crt to your desktop and your server.crt to your server.

Put server.crt in /etc/openvpn/server.crt

OpenVPN configuration

  1. Example Server
  2. Example Client

Let's unpack the server options

We run as the openvpn user (in some systems, you run as 'nobody'

user  nobody
group nogroup

dev : Specifies What type of virtual interface to use (tun/tap)
Only use tap if you want to transparently tunnel ethernet segments

dev tun

Persist the state of the key and the tunnel interface accross server reboots
Initializing these requires root and openvpn is no longer running as root


Topology of the network
Either subnet, or point to point

topology subnet

Listening port
Some public wifi blocks non-web ports, workaround: use udp53 or tcp443

port 1194

Protocol (tcp/udp)
If the connection between the sites is poor, performance sharply decreases if using tcp

proto udp
explicit-exit-notify 1 # only valid for udp

Connection keep-alive times.
If no traffic for 10s, send a ping, restart after 120s silence

keepalive 10 120

Address for the Virtual Subnet
You can generate a random one with $(ipcalc --random-private=24 -n | cut -d= -f2)


Cipher and protocol hardening

cipher AES-256-CBC
ncp-ciphers AES-256-GCM:AES-256-CBC
tls-version-min 1.2

TLS server options

remote-cert-eku "TLS Web Client Authentication"
ca ca.crt
cert server.crt
key server.key
dh dh.pem
crl-verify crl.pem

TLS authentication (HMAC)
We generate the key with "openvpn --genkey --secret ta.key"
Both the client and the server need this key.

    tls-auth ta.key 0
    auth SHA512

Options to 'push' to clients
Similar concept to DHCP

# Push a single route
;push "route"

# Clients use VPN for all internet connections
push "redirect-gateway def1"

# Clients use this DNS
dhcp-option DNS
dhcp-option DNS

# Each client has a persistent ip
ifconfig-pool-persist ipp.txt

# Custom options for particular clients 
client-config-dir ccd

Miscellanea (can be skipped)

# Logging Directories
status openvpn-status.log
log /var/log/openvpn.log
verb 3

# Client connection script
;client-connect /etc/openvpn/statuschange.sh
;client-disconnect /etc/openvpn/statuschange.sh
script-security 2

# Internally handle client-to-client connections   
# By default, client-to-client connections are routed by the server OS 
# and require iptables FORWARD rules 

Creating a systemd unit

In older versions of GNU/Linux, as long as you have the file `/etc/openvpn/server.conf`

You can start it on boot with
/etc/init.d/openvpn start && update-rc.d openvpn enable

For newer versions of GNU/Linux, we have to deal with systemd. Apparently on a lot of distors such as Fedora, OpenVPN uses a "template based systemd" service unit.

I was slightly confused by how this works, so decided to make my own systemd unit.

My configuration file is in /etc/openvpn/server/openvpn.conf

I then create /etc/systemd/system/openvpn.service

Description=OpenVPN Robust And Highly Flexible Tunneling Application On OpenVPN
After=syslog.target network.target

ExecStart=/usr/sbin/openvpn --daemon --writepid /var/run/openvpn/openvpn.pid --cd /etc/openvpn/ --config openvpn.conf


Then to enable it

[root@SERVER ]# systemctl enable openvpn.service
[root@SERVER ]# systemctl start openvpn.service

Redirecting DNS trafifc

You may wish to force your clients to use a particular DNS. While OpenVPN has an option to "push" DNS settings, the client is still ultimately responsible for this.

We can use iptables to redirect all DNS traffic to your desired resolvers. This is useful for devices that may force you to use a particular server. Also, this technique is not just for VPN servers but will work on any iptables based router/gateway

We run the following commands on the server

Don't redirect traffic to Verisign DNS

iptables -t nat -A PREROUTING -d -p tcp -m tcp --dport 53 -j RETURN
iptables -t nat -A PREROUTING -d -p tcp -m tcp --dport 53 -j RETURN

iptables -t nat -A PREROUTING -d -p udp -m udp --dport 53 -j RETURN
iptables -t nat -A PREROUTING -d -p udp -m udp --dport 53 -j RETURN

Redirect all other DNS

iptables -t nat -A PREROUTING -p tcp -m tcp --dport 53 -j DNAT --to-destination
iptables -t nat -A PREROUTING -p udp -m udp --dport 53 -j DNAT --to-destination

Connecting clients

We can make a `systemd` unit in a fashion analogous to the server. More conveniently, we should be able to import our client `.ovpn` file directly into NetworkManager

Concatenating the client keys to the file

Remember the template file we created for clients?
We can now create a customized openvpn file for the client.

On the client machine (assuming desktop.crt and template have been copied) we can do the following.

Warning: removes encryption from the client key Replace the openssl command with cat if you don't want this

cd $client_path
cat template > desktop.ovpn
echo <cert> >> desktop.ovpn
cat desktop.crt >> desktop.ovpn
echo </cert> >> desktop.ovpn
echo <key> >> desktop.ovpn
openssl rsa -in pki/private/desktop.key >> desktop.ovpn
echo </key> >> desktop.ovpn