OpenVPN — Server & Client Reference

A battle-tested, SSL/TLS-based VPN — providing encrypted site-to-site and remote-access tunnels over UDP or TCP, with a mature PKI, flexible routing, and clients for every platform.

PKI · Easy-RSA · tun/tap · UDP/TCP · full-tunnel · split-tunnel · Linux · Windows · macOS · iOS · Android
01 — How OpenVPN Works

OpenVPN uses TLS for the control channel (authentication, key exchange) and a fast symmetric cipher for the data channel. It creates a virtual network interface (tun0 for routed IP traffic, tap0 for bridged Ethernet) and routes packets through the encrypted tunnel between client and server.

OpenVPN's strength is flexibility — it runs over UDP or TCP, on any port, through almost any firewall, and its PKI model scales from a single home server to thousands of concurrent corporate clients.
ConceptMeaning
PKIPublic Key Infrastructure — the CA, server cert, and per-client certs that authenticate both ends of the tunnel.
CACertificate Authority — the root of trust. Signs all server and client certificates.
tunLayer-3 routed mode. Carries IP packets. Use for most VPN setups.
tapLayer-2 bridged mode. Carries Ethernet frames. Use when you need broadcast or non-IP protocols.
Easy-RSAThe PKI toolkit bundled with OpenVPN for creating and managing CAs and certificates.
tls-cryptA pre-shared key that authenticates and encrypts the TLS handshake itself — stops port scanners from even identifying the server as OpenVPN.
Full tunnelAll client traffic (including internet) routes through the VPN.
Split tunnelOnly traffic to specific subnets routes through the VPN. Everything else uses the client's normal gateway.
CLIENT VPN SERVER PRIVATE NETWORK ┌───────────────┐ ┌──────────────────┐ ┌──────────────┐ │ │ │ │ │ │ │ tun0 │◄─────────────│ tun0 │ │ 10.8.0.0/24 │ │ 10.8.0.2 │ encrypted │ 10.8.0.1 │────────────►│ 192.168.1.x │ │ │ UDP/TCP │ │ routing │ │ └───────────────┘ └──────────────────┘ └──────────────┘ 192.168.x.x (LAN) 203.0.113.5 (public IP)
02 — Installation
Debian / Ubuntu — server and client
sudo apt update && sudo apt install openvpn easy-rsa # Verify openvpn --version
RHEL / Fedora / Rocky / AlmaLinux
sudo dnf install epel-release sudo dnf install openvpn easy-rsa openvpn --version
Arch Linux
sudo pacman -S openvpn easy-rsa
Windows client
# Download the official installer from: https://openvpn.net/community-downloads/ # Run as Administrator — installs the TAP/TUN adapter and OpenVPN GUI # Import a .ovpn profile via the system tray icon → Import file
macOS client
# Tunnelblick (free, open source) # https://tunnelblick.net # Double-click a .ovpn file to import it # Or via Homebrew brew install --cask tunnelblick
iOS / Android
# Install "OpenVPN Connect" from the App Store or Google Play # Import the .ovpn profile file via AirDrop, email, or QR code
03 — Building the PKI with Easy-RSA

Every OpenVPN deployment needs a PKI — a CA that signs a server certificate and one certificate per client. Easy-RSA makes this manageable from the command line. Do all PKI work on a secure machine, ideally not the VPN server itself.

Initialise the PKI directory
# Copy Easy-RSA to a working directory make-cadir ~/openvpn-ca cd ~/openvpn-ca # Initialise a fresh PKI ./easyrsa init-pki
Edit vars (optional but recommended)
# ~/openvpn-ca/vars — set your organisation details set_var EASYRSA_REQ_COUNTRY "PT" set_var EASYRSA_REQ_PROVINCE "Porto" set_var EASYRSA_REQ_CITY "Porto" set_var EASYRSA_REQ_ORG "MyOrg" set_var EASYRSA_REQ_EMAIL "admin@example.com" set_var EASYRSA_REQ_OU "VPN" set_var EASYRSA_KEY_SIZE 4096 # RSA key size set_var EASYRSA_ALGO ec # or "rsa" set_var EASYRSA_CURVE secp384r1 # EC curve set_var EASYRSA_CA_EXPIRE 3650 # CA validity: 10 years set_var EASYRSA_CERT_EXPIRE 825 # cert validity: ~2 years
Build the CA
# Build the Certificate Authority — set a strong passphrase when prompted ./easyrsa build-ca # Files created: # pki/ca.crt — CA certificate (distribute to all clients) # pki/private/ca.key — CA private key (NEVER share — keep offline)
Generate the server certificate
# Generate server key + CSR, then sign it with the CA ./easyrsa gen-req server nopass ./easyrsa sign-req server server # Files created: # pki/issued/server.crt # pki/private/server.key
Generate Diffie-Hellman parameters
# DH params for key exchange (only needed if not using ECDH) ./easyrsa gen-dh # File created: pki/dh.pem # This takes a few minutes — grab a coffee
Generate the tls-crypt key
# A pre-shared key for authenticating the TLS handshake # Prevents DoS attacks and hides the server from port scanners openvpn --genkey secret ~/openvpn-ca/pki/ta.key
Generate a client certificate
# Replace "client1" with the actual client name — use one cert per user/device ./easyrsa gen-req client1 nopass ./easyrsa sign-req client client1 # Files created: # pki/issued/client1.crt # pki/private/client1.key # Repeat for each additional client: ./easyrsa gen-req client2 nopass ./easyrsa sign-req client client2
Revoke a client certificate
# Revoke a client (e.g. lost device, terminated employee) ./easyrsa revoke client1 # Regenerate the Certificate Revocation List ./easyrsa gen-crl # Copy the updated CRL to the server and reload OpenVPN sudo cp pki/crl.pem /etc/openvpn/server/ sudo systemctl reload openvpn-server@server
⚠  Keep pki/private/ca.key offline and backed up securely. If your CA key is compromised, every certificate it signed must be replaced. The CA key should never touch the VPN server itself.
04 — Server Setup
Copy PKI files to the server
sudo mkdir -p /etc/openvpn/server # From your PKI machine, copy to the VPN server: sudo cp pki/ca.crt /etc/openvpn/server/ sudo cp pki/issued/server.crt /etc/openvpn/server/ sudo cp pki/private/server.key /etc/openvpn/server/ sudo cp pki/dh.pem /etc/openvpn/server/ # if using RSA sudo cp pki/ta.key /etc/openvpn/server/ sudo cp pki/crl.pem /etc/openvpn/server/ # Lock down permissions sudo chmod 600 /etc/openvpn/server/server.key sudo chmod 600 /etc/openvpn/server/ta.key
/etc/openvpn/server/server.conf — full annotated config
# ── Network ──────────────────────────────────────────────────────────── # Listen on UDP 1194 (change port to avoid filters; use TCP 443 to bypass strict firewalls) port 1194 proto udp # udp is faster; use tcp if UDP is blocked dev tun # layer-3 routed tunnel # ── PKI ──────────────────────────────────────────────────────────────── ca /etc/openvpn/server/ca.crt cert /etc/openvpn/server/server.crt key /etc/openvpn/server/server.key dh /etc/openvpn/server/dh.pem # omit if using ECDH (ecdh-curve directive) crl-verify /etc/openvpn/server/crl.pem # TLS authentication key — must match direction on client (server=0, client=1) tls-crypt /etc/openvpn/server/ta.key # ── Tunnel addressing ────────────────────────────────────────────────── # VPN subnet — clients get IPs from this range server 10.8.0.0 255.255.255.0 # Persist IP assignments across reconnects ifconfig-pool-persist /var/log/openvpn/ipp.txt # ── Routing ──────────────────────────────────────────────────────────── # OPTION A — Full tunnel: route ALL client traffic through the VPN push "redirect-gateway def1 bypass-dhcp" push "dhcp-option DNS 1.1.1.1" push "dhcp-option DNS 8.8.8.8" # OPTION B — Split tunnel: only route specific subnets through the VPN # (comment out the redirect-gateway line above, uncomment these) # push "route 192.168.1.0 255.255.255.0" # push "route 10.0.0.0 255.255.0.0" # Allow clients to reach each other through the VPN client-to-client # ── Keepalive & reliability ──────────────────────────────────────────── keepalive 10 120 # ping every 10s, assume dead after 120s persist-key persist-tun # ── Security ─────────────────────────────────────────────────────────── cipher AES-256-GCM # modern AEAD cipher auth SHA256 # HMAC digest tls-version-min 1.2 # refuse TLS < 1.2 tls-cipher TLS-ECDHE-ECDSA-WITH-AES-256-GCM-SHA384:TLS-ECDHE-RSA-WITH-AES-256-GCM-SHA384 # Drop privileges after startup user nobody group nogroup # use "nobody" on RHEL/Fedora # ── Logging ──────────────────────────────────────────────────────────── status /var/log/openvpn/openvpn-status.log log-append /var/log/openvpn/openvpn.log verb 3 # verbosity: 0=silent, 3=normal, 6=debug mute 20 # suppress repeated log lines # ── Misc ─────────────────────────────────────────────────────────────── max-clients 100 # maximum simultaneous connections compress lz4-v2 # compression (disable if all traffic is already encrypted) push "compress lz4-v2"
Enable IP forwarding and NAT
# Enable IP forwarding permanently echo "net.ipv4.ip_forward = 1" | sudo tee /etc/sysctl.d/99-openvpn.conf sudo sysctl -p /etc/sysctl.d/99-openvpn.conf # NAT — masquerade VPN client traffic through the server's public interface # Replace eth0 with your actual internet-facing interface # iptables sudo iptables -t nat -A POSTROUTING -s 10.8.0.0/24 -o eth0 -j MASQUERADE sudo iptables -A FORWARD -i tun0 -j ACCEPT sudo iptables -A FORWARD -o tun0 -j ACCEPT # Make iptables rules persistent sudo netfilter-persistent save # nftables equivalent sudo nft add table ip nat sudo nft add chain ip nat postrouting '{ type nat hook postrouting priority srcnat; }' sudo nft add rule ip nat postrouting ip saddr 10.8.0.0/24 oifname "eth0" masquerade
Firewall — open the OpenVPN port
# ufw sudo ufw allow 1194/udp sudo ufw reload # firewalld sudo firewall-cmd --zone=public --add-service=openvpn --permanent sudo firewall-cmd --reload # iptables sudo iptables -A INPUT -p udp --dport 1194 -j ACCEPT
Create log directory and start the server
sudo mkdir -p /var/log/openvpn # Start and enable the server sudo systemctl enable --now openvpn-server@server # Check status sudo systemctl status openvpn-server@server # Follow the log sudo tail -f /var/log/openvpn/openvpn.log
✔  The @server suffix in the systemd unit name corresponds to the config filename — /etc/openvpn/server/server.conf. If you named your config myvpn.conf, the unit would be openvpn-server@myvpn.
05 — Per-Client Configuration Files

Each client needs a .ovpn profile — a single portable file that bundles the connection config, CA certificate, client certificate, client key, and TLS key together. Generate one per client, distribute it securely, and keep it secret.

client1.ovpn — inline certificate format (recommended)
client dev tun proto udp remote YOUR_SERVER_IP_OR_DOMAIN 1194 # ← replace with your server's public IP resolv-retry infinite nobind persist-key persist-tun # Security — must match server cipher AES-256-GCM auth SHA256 tls-version-min 1.2 # TLS key direction (client must use direction 1, server uses 0) key-direction 1 verb 3 # ── Inline certificates ───────────────────────────────────────────── # Paste the contents of each file between the tags below <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-crypt> -----BEGIN OpenVPN Static key V1----- (paste contents of ta.key here) -----END OpenVPN Static key V1----- </tls-crypt>
Script — auto-generate a client .ovpn file
#!/bin/bash # gen-client.sh — run from ~/openvpn-ca # Usage: ./gen-client.sh client1 CLIENT=$1 PKI=~/openvpn-ca/pki SERVER_IP="YOUR_SERVER_IP" SERVER_PORT="1194" PROTO="udp" OUTPUT=~/${CLIENT}.ovpn cat > "$OUTPUT" <<EOF client dev tun proto ${PROTO} remote ${SERVER_IP} ${SERVER_PORT} resolv-retry infinite nobind persist-key persist-tun cipher AES-256-GCM auth SHA256 tls-version-min 1.2 key-direction 1 verb 3 EOF echo "<ca>" >> "$OUTPUT" cat "$PKI/ca.crt" >> "$OUTPUT" echo "</ca>" >> "$OUTPUT" echo "<cert>" >> "$OUTPUT" # Extract only the certificate block sed -n '/-----BEGIN CERTIFICATE-----/,/-----END CERTIFICATE-----/p' \ "$PKI/issued/${CLIENT}.crt" >> "$OUTPUT" echo "</cert>" >> "$OUTPUT" echo "<key>" >> "$OUTPUT" cat "$PKI/private/${CLIENT}.key" >> "$OUTPUT" echo "</key>" >> "$OUTPUT" echo "<tls-crypt>" >> "$OUTPUT" cat "$PKI/ta.key" >> "$OUTPUT" echo "</tls-crypt>" >> "$OUTPUT" chmod 600 "$OUTPUT" echo "Profile written to $OUTPUT"
# Generate profiles for each client chmod +x gen-client.sh ./gen-client.sh client1 ./gen-client.sh client2
⚠  The .ovpn file contains the client's private key — treat it like a password. Transfer it only over encrypted channels (SCP, SFTP, Signal). Never send it by email or store it in a public location.
06 — Client Setup — Linux
Install OpenVPN client
# Debian / Ubuntu sudo apt install openvpn # RHEL / Fedora sudo dnf install openvpn
Connect from the command line
# Connect using the .ovpn profile (runs in foreground) sudo openvpn --config client1.ovpn # Connect in the background sudo openvpn --config client1.ovpn --daemon # With verbose logging to see what's happening sudo openvpn --config client1.ovpn --verb 6
Connect via systemd (persistent, auto-start)
# Place the config in the OpenVPN client directory sudo cp client1.ovpn /etc/openvpn/client/client1.conf # Enable and start sudo systemctl enable --now openvpn-client@client1 # Check status sudo systemctl status openvpn-client@client1 # Follow the log sudo journalctl -u openvpn-client@client1 -f
Verify the tunnel is working
# A tun0 interface should appear ip addr show tun0 # The VPN server's gateway should be in your routing table ip route show # Ping the VPN server's tunnel IP ping 10.8.0.1 # Full-tunnel: check your public IP has changed curl https://ifconfig.me # Split-tunnel: verify VPN routes are present ip route | grep 10.8
Disconnect
# If running in foreground — Ctrl+C # If running as a daemon sudo pkill openvpn # If running as a systemd service sudo systemctl stop openvpn-client@client1
07 — Client Setup — Windows
Install and import
# 1. Download OpenVPN GUI from https://openvpn.net/community-downloads/ # 2. Run the installer as Administrator # 3. Copy client1.ovpn to: # C:\Users\YourName\OpenVPN\config\client1.ovpn # (the GUI also accepts drag-and-drop import) # 4. Right-click the OpenVPN system tray icon → Connect
Command line (OpenVPN 2.5+)
# Run PowerShell as Administrator cd "C:\Program Files\OpenVPN\bin" # Connect .\openvpn.exe --config "C:\Users\YourName\OpenVPN\config\client1.ovpn" # Connect in background (as a Windows service) # Use the GUI or: sc start OpenVPNService
Verify on Windows
# In PowerShell or cmd ipconfig # look for a "TAP-Windows Adapter" or "OpenVPN Wintun" # with a 10.8.0.x address route print # check routing table for VPN routes # Check public IP curl https://ifconfig.me
08 — Client Setup — macOS & Mobile
macOS — Tunnelblick
# 1. Install Tunnelblick from https://tunnelblick.net # 2. Double-click client1.ovpn — Tunnelblick imports it automatically # 3. Click the Tunnelblick menu bar icon → Connect client1 # 4. Verify: System Preferences → Network — a new VPN interface appears # Command line via Tunnelblick's openvpn binary: sudo /Applications/Tunnelblick.app/Contents/Resources/openvpn/openvpn-*/openvpn \ --config ~/client1.ovpn
iOS — OpenVPN Connect
# 1. Install "OpenVPN Connect" from the App Store # 2. Transfer client1.ovpn to the device via: # - AirDrop from Mac # - Email attachment (open with OpenVPN Connect) # - Files app from a cloud storage folder # 3. Tap the imported profile → Add → Connect
Android — OpenVPN Connect
# 1. Install "OpenVPN Connect" from Google Play # 2. Transfer client1.ovpn to the device via: # - USB file transfer # - Cloud storage (Google Drive, etc.) # 3. Open the app → + → File → select client1.ovpn → Import → Connect
09 — Full Tunnel vs Split Tunnel

The routing mode is controlled entirely by directives pushed from the server to the client. No client config change is needed when switching between modes.

Full tunnel — all client traffic through the VPN
# In server.conf — push these directives to clients: push "redirect-gateway def1 bypass-dhcp" push "dhcp-option DNS 1.1.1.1" push "dhcp-option DNS 8.8.8.8" # Effect: # Client default route → VPN → server → internet # All DNS queries resolve through the server's DNS # Client's real IP is hidden from the internet # All client traffic flows through your server (use for privacy, remote work)
Split tunnel — only specific subnets through the VPN
# In server.conf — remove redirect-gateway, push specific routes: # push "redirect-gateway def1 bypass-dhcp" ← comment this out push "route 192.168.1.0 255.255.255.0" # reach the office LAN push "route 10.0.0.0 255.255.0.0" # reach internal servers push "dhcp-option DNS 192.168.1.1" # use internal DNS for VPN traffic # Effect: # Traffic to 192.168.1.x and 10.0.x.x → VPN # Everything else → client's normal internet connection # Lower load on the VPN server # Client retains local internet speed for browsing
Per-client routing overrides
# Push different routes to a specific client using a client config directory # In server.conf: client-config-dir /etc/openvpn/server/ccd # /etc/openvpn/server/ccd/client1 # Assign a fixed IP to client1 ifconfig-push 10.8.0.10 10.8.0.11 # Push an extra route only to client1 push "route 172.16.0.0 255.255.0.0" # /etc/openvpn/server/ccd/client2 ifconfig-push 10.8.0.20 10.8.0.21
ℹ  Fixed IP assignments via ccd are useful when a client is a server itself — you always know its VPN IP and can write firewall rules based on it.
10 — Server Management
Monitor connected clients
# Read the status file (updates every 60s by default) sudo cat /var/log/openvpn/openvpn-status.log # Follow the main log sudo tail -f /var/log/openvpn/openvpn.log # Via systemd journal sudo journalctl -u openvpn-server@server -f # List active connections using the management interface (if enabled) # Add to server.conf: management localhost 7505 echo "status" | nc localhost 7505
Enable the management interface
# In server.conf — add: management localhost 7505 management-client-auth # optional: require client auth on management socket # Restart server to activate sudo systemctl restart openvpn-server@server # Connect and issue commands nc localhost 7505 # Commands: # status — list connected clients # kill client1 — forcibly disconnect a client # log 10 — show last 10 log lines # quit — exit
Forcibly disconnect a client
# Via management interface echo "kill client1" | nc localhost 7505 # Or by revoking their cert and reloading (permanent) cd ~/openvpn-ca ./easyrsa revoke client1 ./easyrsa gen-crl sudo cp pki/crl.pem /etc/openvpn/server/ sudo systemctl reload openvpn-server@server
Reload config without dropping connections
# SIGHUP causes OpenVPN to re-read its config and CRL sudo systemctl reload openvpn-server@server # or: sudo kill -HUP $(pgrep -f "openvpn.*server")
11 — Advanced Server Options
Run OpenVPN on TCP 443 (bypass deep packet inspection)
# In server.conf: port 443 proto tcp # Note: TCP over TCP (VPN over TCP tunnel) can cause performance issues # under packet loss. Only use TCP 443 if UDP is blocked.
Multiple OpenVPN instances on one server
# Create a second config file sudo cp /etc/openvpn/server/server.conf /etc/openvpn/server/server-tcp.conf # Edit server-tcp.conf — change port and proto port 443 proto tcp server 10.9.0.0 255.255.255.0 # different VPN subnet status /var/log/openvpn/openvpn-tcp-status.log # Start second instance sudo systemctl enable --now openvpn-server@server-tcp
ECDH — drop static DH parameters (OpenVPN 2.4.8+)
# In server.conf — replace "dh dh.pem" with: dh none ecdh-curve secp384r1 # No need to generate or distribute dh.pem # Perfect Forward Secrecy via ECDHE key exchange
Two-factor authentication with OTP
# Install google-authenticator PAM module sudo apt install libpam-google-authenticator # In server.conf — add: plugin /usr/lib/openvpn/openvpn-plugin-auth-pam.so openvpn reneg-sec 0 # In client.ovpn — add: auth-user-pass # prompts for username + OTP as password # Configure PAM for OpenVPN: # /etc/pam.d/openvpn auth required pam_google_authenticator.so
Restrict which clients can connect (certificate CN matching)
# In server.conf — only allow certs with matching CN patterns: tls-verify "/etc/openvpn/server/verify-cn.sh" # /etc/openvpn/server/verify-cn.sh #!/bin/bash # $1 = depth, $2 = CN if [ "$1" = "0" ]; then case "$2" in client*) exit 0 ;; # allow any CN starting with "client" *) exit 1 ;; esac fi exit 0
12 — Firewall Integration

On the server, three things need to be in place: the VPN port must be open inbound, IP forwarding must be enabled, and the VPN subnet must be NAT-masqueraded through the server's internet interface.

ufw — complete server firewall setup
# Allow OpenVPN UDP port sudo ufw allow 1194/udp # Allow forwarding — edit /etc/default/ufw: DEFAULT_FORWARD_POLICY="ACCEPT" # Add NAT rule at the top of /etc/ufw/before.rules (before *filter): *nat :POSTROUTING ACCEPT [0:0] -A POSTROUTING -s 10.8.0.0/24 -o eth0 -j MASQUERADE COMMIT # Enable ufw forwarding in /etc/ufw/sysctl.conf: net/ipv4/ip_forward=1 # Reload sudo ufw disable && sudo ufw enable
firewalld — complete server firewall setup
# Allow OpenVPN service sudo firewall-cmd --zone=public --add-service=openvpn --permanent # Enable masquerade on the external zone sudo firewall-cmd --zone=external --add-masquerade --permanent # Add VPN interface to the internal zone sudo firewall-cmd --zone=internal --add-interface=tun0 --permanent # Allow forwarding from VPN zone to external sudo firewall-cmd --new-policy=vpn-to-wan --permanent sudo firewall-cmd --policy=vpn-to-wan --add-ingress-zone=internal --permanent sudo firewall-cmd --policy=vpn-to-wan --add-egress-zone=external --permanent sudo firewall-cmd --policy=vpn-to-wan --set-target=ACCEPT --permanent sudo firewall-cmd --reload
nftables — complete server firewall setup
table inet firewall { chain input { type filter hook input priority filter; policy drop; iifname "lo" accept ct state { established, related } accept udp dport 1194 accept # OpenVPN # SSH (adjust to your admin port) tcp dport 22 ct state new accept } chain forward { type filter hook forward priority filter; policy drop; # Allow forwarding between tun0 and eth0 iifname "tun0" oifname "eth0" accept iifname "eth0" oifname "tun0" ct state { established, related } accept } } table ip nat { chain postrouting { type nat hook postrouting priority srcnat; ip saddr 10.8.0.0/24 oifname "eth0" masquerade } }
13 — Troubleshooting
SymptomCause & Fix
TLS handshake failedClock skew between client and server (certificates are time-sensitive) — sync NTP on both. Also check tls-crypt key direction (server=0, client must use key-direction 1).
VERIFY ERROR: certificate not yet validSystem clock is wrong. Run timedatectl to check and sync NTP.
Connection established but no internet (full tunnel)IP forwarding not enabled (sysctl net.ipv4.ip_forward must be 1) or NAT masquerade rule missing.
Connection established but can't reach LAN (split tunnel)Missing push "route ..." directives in server.conf, or the LAN hosts have no route back to the VPN subnet.
DNS doesn't work through VPNpush "dhcp-option DNS ..." not in server.conf, or the client OS isn't applying the pushed DNS servers (common on Linux — see update-resolv-conf script).
Client connects then immediately dropsCheck keepalive settings and firewall UDP timeout. Try TCP mode to rule out UDP filtering.
CERTIFICATE_VERIFY_FAILEDWrong CA cert, cert expired, or CRL is outdated — regenerate with easyrsa gen-crl and copy to server.
Cannot connect — no response from serverUDP port 1194 blocked by firewall. Try TCP 443. Verify with: nc -u server-ip 1194.
Multiple clients: one drops when another connectsMissing duplicate-cn directive (same cert used twice) or IP pool exhausted.
tun0 does not appear on clientTUN kernel module not loaded — sudo modprobe tun. On containers, verify TUN device is available.
Diagnostic commands
# Run OpenVPN with maximum verbosity (run as test — not in production) sudo openvpn --config server.conf --verb 9 # Check TUN module is loaded lsmod | grep tun sudo modprobe tun # Verify server is listening sudo ss -ulnp | grep 1194 # UDP sudo ss -tlnp | grep 1194 # TCP # Check IP forwarding sysctl net.ipv4.ip_forward # View active NAT rules sudo iptables -t nat -L -v -n sudo nft list table ip nat # Trace a packet through the tunnel sudo tcpdump -i tun0 # Test UDP connectivity to the server from a client machine nc -u server-ip 1194
14 — Quick Reference
CommandWhat it does
./easyrsa init-pkiInitialise a new PKI directory
./easyrsa build-caCreate the Certificate Authority
./easyrsa gen-req server nopassGenerate server key + CSR
./easyrsa sign-req server serverSign the server CSR with the CA
./easyrsa gen-req clientN nopassGenerate a client key + CSR
./easyrsa sign-req client clientNSign a client CSR with the CA
./easyrsa revoke clientNRevoke a client certificate
./easyrsa gen-crlRegenerate the Certificate Revocation List
openvpn --genkey secret ta.keyGenerate the tls-crypt pre-shared key
systemctl enable --now openvpn-server@serverStart and enable the OpenVPN server
systemctl status openvpn-server@serverCheck server status
systemctl reload openvpn-server@serverReload config / CRL without dropping connections
sudo openvpn --config client1.ovpnConnect a Linux client (foreground)
systemctl enable --now openvpn-client@client1Connect a Linux client as a persistent service
journalctl -u openvpn-server@server -fFollow server log
✔  The complete setup order: build CA → generate server cert → generate DH + tls-crypt → configure server.conf → enable IP forwarding + NAT → open firewall port → start server → generate client cert → create .ovpn profile → test connection → disable password auth on clients → keep CA key offline.