OpenVPN server

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 1194port 443 and proto udpproto 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

FileGoes toPurpose
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.