Chapter 5 — OpenVPN Server
WireGuard's one weakness is that it is UDP-only. Some corporate networks,
hotel captive portals, and restrictive ISPs block all UDP traffic except DNS.
OpenVPN's killer feature is the ability to run over TCP port 443
— identical to HTTPS traffic — which passes through virtually every firewall
on the planet. This chapter builds a complete OpenVPN server from scratch.
How OpenVPN Authentication Works — PKI
Unlike WireGuard's simple key pairs, OpenVPN uses a
Public Key Infrastructure (PKI) — a Certificate Authority
that signs certificates for both the server and each client. The server
and clients trust each other because they both hold certificates signed
by the same CA.
CA
Certificate Authority
ca.crt + ca.key
↙ ↘
Server cert
server.crt + server.key
Client cert
client1.crt + client1.key
CA signs both → server and client verify each other's certificate against the CA
Easy-RSA is the tool bundled with OpenVPN for managing
this PKI. It handles the CA creation, certificate signing, and revocation
list — without needing a deep understanding of raw openssl
commands.
Step 1 — Install OpenVPN and Easy-RSA
server — install packages
root@server:~# apt update && apt install -y openvpn easy-rsa
# confirm versions
root@server:~# openvpn --version | head -1
OpenVPN 2.6.3 x86_64-pc-linux-gnu
root@server:~# /usr/share/easy-rsa/easyrsa --version
EasyRSA Version: 3.1.7
Step 2 — Build the PKI and Certificate Authority
server — create PKI and CA
# create a working directory for the PKI
root@server:~# make-cadir ~/openvpn-pki && cd ~/openvpn-pki
# initialise the PKI structure
root@server:~/openvpn-pki# ./easyrsa init-pki
init-pki complete; you may now create a CA or requests.
# build the CA — you'll be asked for a CA name (e.g. "HomeVPN")
root@server:~/openvpn-pki# ./easyrsa build-ca nopass
CA creation complete. Your new CA certificate file is at:
pki/ca.crt
nopass skips the CA key passphrase — convenient for
automated setups but means anyone who gets ca.key can sign
arbitrary certificates. For a production setup, omit nopass
and store the CA passphrase securely in a password manager.
Step 3 — Generate the Server Certificate
server — server certificate + DH params + TLS auth key
# generate and sign the server certificate (name it "server")
root@server:~/openvpn-pki# ./easyrsa gen-req server nopass
root@server:~/openvpn-pki# ./easyrsa sign-req server server
Certificate created at: pki/issued/server.crt
# generate Diffie-Hellman parameters (takes a minute)
root@server:~/openvpn-pki# ./easyrsa gen-dh
DH parameters of size 2048 created at pki/dh.pem
# generate a TLS authentication key — extra HMAC layer against DoS/port scanning
root@server:~/openvpn-pki# openvpn --genkey secret pki/ta.key
# copy everything the server needs to /etc/openvpn/server/
root@server:~/openvpn-pki# cp pki/ca.crt pki/issued/server.crt pki/private/server.key \
pki/dh.pem pki/ta.key /etc/openvpn/server/
Step 4 — Generate a Client Certificate
server — client certificate (repeat for each client)
# generate client key and certificate request
root@server:~/openvpn-pki# ./easyrsa gen-req client1 nopass
# sign it with the CA
root@server:~/openvpn-pki# ./easyrsa sign-req client client1
Certificate created at: pki/issued/client1.crt
# the files needed for the client .ovpn profile:
# pki/ca.crt — CA certificate (verify the server)
# pki/issued/client1.crt — client certificate
# pki/private/client1.key — client private key (KEEP SECRET)
# pki/ta.key — TLS auth key
Step 5 — Write the Server Config
# /etc/openvpn/server/server.conf
# ── Network ───────────────────────────────────────────────
port 1194 # change to 443 for firewall bypass (requires proto tcp)
proto udp # change to tcp for port 443 mode
dev tun
# ── Certificates ──────────────────────────────────────────
ca /etc/openvpn/server/ca.crt
cert /etc/openvpn/server/server.crt
key /etc/openvpn/server/server.key
dh /etc/openvpn/server/dh.pem
tls-auth /etc/openvpn/server/ta.key 0 # 0 = server side
# ── VPN subnet ────────────────────────────────────────────
server 10.8.0.0 255.255.255.0 # assigns 10.8.0.1 to server, clients get 10.8.0.x
ifconfig-pool-persist /var/log/openvpn/ipp.txt
# ── Routing — push settings to clients ────────────────────
push "redirect-gateway def1 bypass-dhcp" # full tunnel — all traffic via VPN
push "dhcp-option DNS 8.8.8.8" # push Google DNS (or use 10.8.0.1)
push "dhcp-option DNS 8.8.4.4"
# ── Security ──────────────────────────────────────────────
cipher AES-256-GCM
auth SHA256
tls-version-min 1.2
tls-cipher TLS-ECDHE-RSA-WITH-AES-256-GCM-SHA384
key-direction 0
# ── Hardening ─────────────────────────────────────────────
user nobody
group nogroup
persist-key
persist-tun
# ── Performance & reliability ──────────────────────────────
keepalive 10 120
compress lz4-v2
push "compress lz4-v2"
max-clients 10
# ── Logging ───────────────────────────────────────────────
status /var/log/openvpn/status.log
log-append /var/log/openvpn/openvpn.log
verb 3
Firewall bypass mode: to run on TCP 443, change
port 1194 → port 443 and
proto udp → proto tcp. Note that
TCP-over-TCP (tunnelling TCP application traffic inside a TCP tunnel)
causes performance issues on lossy connections — prefer UDP 1194 when
firewalls allow it.
Step 6 — Enable IP Forwarding and Start OpenVPN
server — firewall, IP forwarding, start service
# IP forwarding (same as WireGuard — skip if already done)
root@server:~# echo "net.ipv4.ip_forward=1" >> /etc/sysctl.conf && sysctl -p
# iptables NAT rule — replace eth0 with your external interface
root@server:~# iptables -t nat -A POSTROUTING -s 10.8.0.0/24 -o eth0 -j MASQUERADE
# make the iptables rule persistent across reboots
root@server:~# apt install -y iptables-persistent && netfilter-persistent save
# open the firewall
root@server:~# ufw allow 1194/udp
# or for TCP 443 mode:
root@server:~# ufw allow 443/tcp
# create log directory
root@server:~# mkdir -p /var/log/openvpn
# start and enable OpenVPN
root@server:~# systemctl enable --now openvpn-server@server
root@server:~# systemctl status openvpn-server@server
● openvpn-server@server.service - OpenVPN service for server
Active: active (running) since Thu 2026-06-18 11:05:44 BST
Step 7 — Create the Client .ovpn Profile
The cleanest approach is an inline .ovpn
file — all certificates and keys embedded in the single file, so the client
only needs one file to import:
# client1.ovpn — all-in-one inline profile
client
dev tun
proto udp # match server (udp or tcp)
remote your.server.ip 1194 # server public IP and port
resolv-retry infinite
nobind
persist-key
persist-tun
remote-cert-tls server # verify server cert has server flag
cipher AES-256-GCM
auth SHA256
key-direction 1 # 1 = client side (server uses 0)
verb 3
<ca>
-----BEGIN CERTIFICATE-----
...paste contents of ca.crt here...
-----END CERTIFICATE-----
</ca>
<cert>
-----BEGIN CERTIFICATE-----
...paste contents of client1.crt here...
-----END CERTIFICATE-----
</cert>
<key>
-----BEGIN PRIVATE KEY-----
...paste contents of client1.key here...
-----END PRIVATE KEY-----
</key>
<tls-auth>
-----BEGIN OpenVPN Static key V1-----
...paste contents of ta.key here...
-----END OpenVPN Static key V1-----
</tls-auth>
Automating .ovpn generation with a script
#!/bin/bash
# generate-client.sh — usage: ./generate-client.sh client1
CLIENT=$1
PKI=~/openvpn-pki
SERVER_IP=your.server.ip
cat > ~/${CLIENT}.ovpn <<EOF
client
dev tun
proto udp
remote ${SERVER_IP} 1194
resolv-retry infinite
nobind
persist-key
persist-tun
remote-cert-tls server
cipher AES-256-GCM
auth SHA256
key-direction 1
verb 3
<ca>
$(cat ${PKI}/pki/ca.crt)
</ca>
<cert>
$(cat ${PKI}/pki/issued/${CLIENT}.crt)
</cert>
<key>
$(cat ${PKI}/pki/private/${CLIENT}.key)
</key>
<tls-auth>
$(cat ${PKI}/pki/ta.key)
</tls-auth>
EOF
echo "Profile written to ~/${CLIENT}.ovpn"
PKI File Reference
| File | Goes to | Purpose |
| pki/ca.crt |
Server + Client |
CA certificate — both sides verify each other's cert against this |
| pki/issued/server.crt |
Server only |
Server's identity certificate, signed by the CA |
| pki/private/server.key |
Server only — never share |
Server's private key |
| pki/dh.pem |
Server only |
Diffie-Hellman parameters for key exchange |
| pki/ta.key |
Server + Client |
TLS-auth HMAC key — shared secret, direction 0 (server) / 1 (client) |
| pki/issued/client1.crt |
Client only |
Client's identity certificate, embedded in the .ovpn profile |
| pki/private/client1.key |
Client only — never share |
Client's private key, embedded in the .ovpn profile |
Revoking a Client Certificate
One advantage of PKI over WireGuard's key model: you can revoke a
specific client's access without affecting any other client or regenerating
the CA:
server — revoke a client certificate
# revoke client1's certificate
root@server:~/openvpn-pki# ./easyrsa revoke client1
# regenerate the Certificate Revocation List
root@server:~/openvpn-pki# ./easyrsa gen-crl
root@server:~/openvpn-pki# cp pki/crl.pem /etc/openvpn/server/
# add this line to server.conf if not already present, then restart
root@server:~# echo "crl-verify /etc/openvpn/server/crl.pem" >> /etc/openvpn/server/server.conf
root@server:~# systemctl restart openvpn-server@server
# client1 can no longer connect — their certificate is now on the revocation list
WireGuard vs OpenVPN for revocation: in WireGuard you
revoke access by removing the peer's [Peer] block from
wg0.conf and running wg set wg0 peer <key> remove.
OpenVPN's CRL approach is more formal but serves the same purpose.
Neither is inherently better — it depends on whether you prefer file-based
or PKI-based access management.
Next — Chapter 6: OpenVPN Clients.
The server is running and the .ovpn profile is ready.
Chapter 6 covers connecting from Linux (openvpn --config
and NetworkManager), the Windows GUI client, and the anatomy of the
.ovpn file so you can customise it confidently.