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.