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.
# create a working directory for the PKIroot@server:~#make-cadir ~/openvpn-pki && cd ~/openvpn-pki# initialise the PKI structureroot@server:~/openvpn-pki#./easyrsa init-pkiinit-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 nopassCA 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 nopassroot@server:~/openvpn-pki#./easyrsa sign-req server serverCertificate created at: pki/issued/server.crt# generate Diffie-Hellman parameters (takes a minute)root@server:~/openvpn-pki#./easyrsa gen-dhDH parameters of size 2048 created at pki/dh.pem# generate a TLS authentication key — extra HMAC layer against DoS/port scanningroot@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 requestroot@server:~/openvpn-pki#./easyrsa gen-req client1 nopass# sign it with the CAroot@server:~/openvpn-pki#./easyrsa sign-req client client1Certificate 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 ───────────────────────────────────────────────port1194# change to 443 for firewall bypass (requires proto tcp)protoudp# change to tcp for port 443 modedevtun# ── Certificates ──────────────────────────────────────────ca/etc/openvpn/server/ca.crtcert/etc/openvpn/server/server.crtkey/etc/openvpn/server/server.keydh/etc/openvpn/server/dh.pemtls-auth/etc/openvpn/server/ta.key0# 0 = server side# ── VPN subnet ────────────────────────────────────────────server10.8.0.0 255.255.255.0# assigns 10.8.0.1 to server, clients get 10.8.0.xifconfig-pool-persist/var/log/openvpn/ipp.txt# ── Routing — push settings to clients ────────────────────push"redirect-gateway def1 bypass-dhcp"# full tunnel — all traffic via VPNpush"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 ──────────────────────────────────────────────cipherAES-256-GCMauthSHA256tls-version-min1.2tls-cipherTLS-ECDHE-RSA-WITH-AES-256-GCM-SHA384key-direction0# ── Hardening ─────────────────────────────────────────────usernobodygroupnogrouppersist-keypersist-tun# ── Performance & reliability ──────────────────────────────keepalive10 120compresslz4-v2push"compress lz4-v2"max-clients10# ── Logging ───────────────────────────────────────────────status/var/log/openvpn/status.loglog-append/var/log/openvpn/openvpn.logverb3
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 interfaceroot@server:~#iptables -t nat -A POSTROUTING -s 10.8.0.0/24 -o eth0 -j MASQUERADE# make the iptables rule persistent across rebootsroot@server:~#apt install -y iptables-persistent && netfilter-persistent save# open the firewallroot@server:~#ufw allow 1194/udp# or for TCP 443 mode:root@server:~#ufw allow 443/tcp# create log directoryroot@server:~#mkdir -p /var/log/openvpn# start and enable OpenVPNroot@server:~#systemctl enable --now openvpn-server@serverroot@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 profileclientdev tun
protoudp# match server (udp or tcp)remoteyour.server.ip1194# server public IP and portresolv-retry infinite
nobindpersist-keypersist-tunremote-cert-tls server # verify server cert has server flagcipher AES-256-GCM
auth SHA256
key-direction1# 1 = client side (server uses 0)verb3<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
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 certificateroot@server:~/openvpn-pki#./easyrsa revoke client1# regenerate the Certificate Revocation Listroot@server:~/openvpn-pki#./easyrsa gen-crlroot@server:~/openvpn-pki#cp pki/crl.pem /etc/openvpn/server/# add this line to server.conf if not already present, then restartroot@server:~#echo "crl-verify /etc/openvpn/server/crl.pem" >> /etc/openvpn/server/server.confroot@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.