OpenVPN Configuration on Linux

OpenVPN is the most widely deployed open-source VPN solution. It operates in user space over TLS, supports both routed (tun) and bridged (tap) modes, and runs on virtually every operating system. Although it cannot match WireGuard's raw throughput, its flexibility, extensive plugin system, and broad client support make it the right choice in many enterprise environments.

This guide covers a full deployment: building a PKI with Easy-RSA, configuring the server and clients, hardening with tls-auth / tls-crypt, pushing routes and DNS, and revoking certificates.

Part of the VPN and SSH guide series. See also: WireGuard Setup | SSH Tunneling | VPN Protocol Comparison


Installation

# Debian / Ubuntu
sudo apt update
sudo apt install openvpn easy-rsa

# RHEL / Fedora
sudo dnf install openvpn easy-rsa

Building the PKI with Easy-RSA

Initialise a new PKI directory:

make-cadir ~/openvpn-ca && cd ~/openvpn-ca

# Edit vars (optional -- set defaults for country, org, etc.)
nano vars

# Initialise and build the CA
./easyrsa init-pki
./easyrsa build-ca nopass

Generate the server certificate and Diffie-Hellman parameters:

./easyrsa gen-req server nopass
./easyrsa sign-req server server
./easyrsa gen-dh

Generate a client certificate:

./easyrsa gen-req client1 nopass
./easyrsa sign-req client client1

Generate the TLS authentication key (HMAC firewall):

openvpn --genkey secret ta.key

Copy the relevant files to /etc/openvpn/server/:

sudo cp pki/ca.crt pki/issued/server.crt pki/private/server.key \
       pki/dh.pem ta.key /etc/openvpn/server/

Server Configuration

Create /etc/openvpn/server/server.conf:

port 1194
proto udp
dev tun

ca      ca.crt
cert    server.crt
key     server.key
dh      dh.pem

# TLS hardening -- use tls-crypt for newer clients
tls-auth ta.key 0
# tls-crypt ta.key    # alternative: encrypts control channel entirely

cipher AES-256-GCM
auth   SHA256
data-ciphers AES-256-GCM:AES-128-GCM:CHACHA20-POLY1305

server 10.8.0.0 255.255.255.0
topology subnet

# Push routes and DNS to clients
push "redirect-gateway def1 bypass-dhcp"
push "dhcp-option DNS 1.1.1.1"
push "dhcp-option DNS 9.9.9.9"

# Maintain a persistent tunnel
keepalive 10 120
persist-key
persist-tun

# Logging
status      /var/log/openvpn/status.log
log-append  /var/log/openvpn/server.log
verb 3

# Drop privileges after init
user  nobody
group nogroup

# Allow multiple clients to share a certificate (not recommended for production)
# duplicate-cn

Key directives explained

Directive Purpose
server 10.8.0.0 255.255.255.0 Assigns the VPN subnet; server gets .1.
topology subnet Uses a /24 subnet instead of point-to-point (net30).
push "redirect-gateway def1" Routes all client traffic through the VPN.
tls-auth ta.key 0 Adds HMAC to every TLS packet (DoS protection). The server uses direction 0, clients use 1.
data-ciphers Negotiated cipher list (OpenVPN 2.5+).

Enable IP forwarding and NAT:

echo "net.ipv4.ip_forward = 1" | sudo tee /etc/sysctl.d/99-openvpn.conf
sudo sysctl --system

sudo iptables -t nat -A POSTROUTING -s 10.8.0.0/24 -o eth0 -j MASQUERADE

Start the server:

sudo systemctl enable --now openvpn-server@server

Client Configuration

Create client1.ovpn:

client
dev tun
proto udp
remote vpn.example.com 1194
resolv-retry infinite
nobind

persist-key
persist-tun

ca      [inline]
cert    [inline]
key     [inline]
tls-auth [inline] 1

cipher AES-256-GCM
auth   SHA256
verb 3

<ca>
-----BEGIN CERTIFICATE-----
... (contents of ca.crt) ...
-----END CERTIFICATE-----
</ca>

<cert>
-----BEGIN CERTIFICATE-----
... (contents of client1.crt) ...
-----END CERTIFICATE-----
</cert>

<key>
-----BEGIN PRIVATE KEY-----
... (contents of client1.key) ...
-----END PRIVATE KEY-----
</key>

<tls-auth>
-----BEGIN OpenVPN Static key V1-----
... (contents of ta.key) ...
-----END OpenVPN Static key V1-----
</tls-auth>

Using [inline] and <tag> blocks produces a single portable .ovpn file that works on Linux, macOS, Windows, Android, and iOS.

Push Directives and Split Tunnelling

To route only specific subnets through the VPN (split tunnel), replace the redirect-gateway push with explicit routes:

push "route 192.168.10.0 255.255.255.0"
push "route 172.16.0.0 255.255.0.0"

Per-client overrides go in the client configuration directory:

mkdir /etc/openvpn/server/ccd
echo 'iroute 192.168.20.0 255.255.255.0' > /etc/openvpn/server/ccd/client1

Add client-config-dir ccd to server.conf.

Certificate Revocation

If a device is lost or a user leaves:

cd ~/openvpn-ca
./easyrsa revoke client1
./easyrsa gen-crl
sudo cp pki/crl.pem /etc/openvpn/server/

Add to server.conf:

crl-verify crl.pem

Reload:

sudo systemctl restart openvpn-server@server

The revoked client will be immediately rejected on its next connection attempt.

Firewall Rules

# Allow OpenVPN traffic
sudo iptables -A INPUT -p udp --dport 1194 -j ACCEPT

# Allow traffic on the tun interface
sudo iptables -A FORWARD -i tun0 -j ACCEPT
sudo iptables -A FORWARD -o tun0 -j ACCEPT

Troubleshooting

Increase verbosity temporarily:

sudo openvpn --config /etc/openvpn/server/server.conf --verb 6

Common issues:

Symptom Fix
TLS key negotiation failed Mismatched tls-auth direction or missing ta.key.
AUTH_FAILED Wrong certificate, expired cert, or CRL mismatch.
Connected but no internet Missing NAT rule or IP forwarding disabled.
Cipher negotiation failed Ensure data-ciphers lists at least one common cipher on both sides.

Return to the VPN and SSH hub for more guides, or continue with SSH Tunneling.