Setting up an OpenVPN server on Ubuntu gives you a battle-tested VPN solution backed by a mature PKI — ideal for remote workers, site-to-site links, and securing traffic on untrusted networks. This guide walks you through every step: building a certificate authority with Easy-RSA, writing a production-ready server.conf, generating client .ovpn profiles, configuring UFW firewall rules, and enabling NAT masquerade so VPN clients reach the internet.
Prerequisites
- Ubuntu 22.04 LTS or 24.04 LTS server with a public IP address
- Sudo or root access
- A domain name or static IP pointing to your server (recommended)
- Basic familiarity with the Linux command line
- UFW installed (default on Ubuntu; install with
sudo apt install ufwif absent)
Installing OpenVPN and Easy-RSA
Start by updating the package list and installing both OpenVPN and the Easy-RSA toolkit:
sudo apt update && sudo apt upgrade -y
sudo apt install openvpn easy-rsa -y
Copy the Easy-RSA template directory to a persistent location under the OpenVPN directory:
sudo mkdir -p /etc/openvpn/easy-rsa
sudo cp -r /usr/share/easy-rsa/* /etc/openvpn/easy-rsa/
cd /etc/openvpn/easy-rsa
Building the PKI with Easy-RSA
A complete OpenVPN PKI consists of a Certificate Authority (CA), a server certificate/key pair, Diffie-Hellman parameters, and an HMAC key for TLS authentication.
Initialize the PKI and create the CA
sudo ./easyrsa init-pki
sudo ./easyrsa build-ca nopass
Accept the default Common Name (Easy-RSA CA) or set your own. The nopass flag skips the CA password prompt — acceptable for automated servers, but add a passphrase in high-security environments.
Generate the server certificate
sudo ./easyrsa gen-req server nopass
sudo ./easyrsa sign-req server server
Type yes to confirm the signing. This produces pki/issued/server.crt and pki/private/server.key.
Generate Diffie-Hellman parameters and the TLS auth key
DH parameters take several minutes to generate:
sudo ./easyrsa gen-dh
sudo openvpn --genkey secret /etc/openvpn/easy-rsa/pki/ta.key
Copy all required keys to the OpenVPN server directory:
sudo cp pki/ca.crt pki/issued/server.crt pki/private/server.key \
pki/dh.pem pki/ta.key /etc/openvpn/server/
Writing server.conf
Create /etc/openvpn/server/server.conf with the following configuration:
# /etc/openvpn/server/server.conf
port 1194
proto udp
dev tun
ca /etc/openvpn/server/ca.crt
cert /etc/openvpn/server/server.crt
key /etc/openvpn/server/server.key
dh /etc/openvpn/server/dh.pem
# VPN subnet — clients receive addresses from this pool
server 10.8.0.0 255.255.255.0
# Push default route (all client traffic through VPN)
push "redirect-gateway def1 bypass-dhcp"
push "dhcp-option DNS 1.1.1.1"
push "dhcp-option DNS 8.8.8.8"
# TLS hardening
tls-auth /etc/openvpn/server/ta.key 0
cipher AES-256-GCM
auth SHA256
tls-version-min 1.2
keepalive 10 120
user nobody
group nogroup
persist-key
persist-tun
# Logging
status /var/log/openvpn/status.log
log-append /var/log/openvpn/openvpn.log
verb 3
Create the log directory and start the service:
sudo mkdir -p /var/log/openvpn
sudo systemctl enable --now openvpn-server@server
sudo systemctl status openvpn-server@server
Firewall Rules and NAT Routing
Enable IP forwarding
Edit /etc/sysctl.conf and uncomment (or add) the following line:
net.ipv4.ip_forward=1
Apply immediately without a reboot:
sudo sysctl -p
Add NAT masquerade rules to UFW
Find your public-facing network interface (commonly eth0 or ens3):
ip route | grep default
Edit /etc/ufw/before.rules and insert the following block before the *filter section:
# OpenVPN NAT
*nat
:POSTROUTING ACCEPT [0:0]
-A POSTROUTING -s 10.8.0.0/24 -o eth0 -j MASQUERADE
COMMIT
Replace eth0 with your actual interface name.
Next, allow packet forwarding in /etc/default/ufw:
DEFAULT_FORWARD_POLICY="ACCEPT"
Open the OpenVPN port and reload UFW:
sudo ufw allow 1194/udp
sudo ufw allow OpenSSH
sudo ufw reload
Generating Client Certificates and .ovpn Files
Create a client certificate
cd /etc/openvpn/easy-rsa
sudo ./easyrsa gen-req client1 nopass
sudo ./easyrsa sign-req client client1
Build the client1.ovpn file
A self-contained .ovpn embeds all certificates inline so the user needs only one file. Create /root/clients/client1.ovpn:
client
dev tun
proto udp
remote YOUR_SERVER_IP 1194
resolv-retry infinite
nobind
persist-key
persist-tun
cipher AES-256-GCM
auth SHA256
key-direction 1
verb 3
<ca>
# paste contents of /etc/openvpn/server/ca.crt here
</ca>
<cert>
# paste contents of /etc/openvpn/easy-rsa/pki/issued/client1.crt here
</cert>
<key>
# paste contents of /etc/openvpn/easy-rsa/pki/private/client1.key here
</key>
<tls-auth>
# paste contents of /etc/openvpn/server/ta.key here
</tls-auth>
You can automate the embedding with a shell script:
#!/bin/bash
# gen-client.sh
CLIENT=$1
OVPN_DIR=/root/clients
KEYS=/etc/openvpn/easy-rsa/pki
cat > "$OVPN_DIR/$CLIENT.ovpn" <<EOF
client
dev tun
proto udp
remote YOUR_SERVER_IP 1194
resolv-retry infinite
nobind
persist-key
persist-tun
cipher AES-256-GCM
auth SHA256
key-direction 1
verb 3
<ca>
$(cat "$KEYS/ca.crt")
</ca>
<cert>
$(openssl x509 -in "$KEYS/issued/$CLIENT.crt")
</cert>
<key>
$(cat "$KEYS/private/$CLIENT.key")
</key>
<tls-auth>
$(cat /etc/openvpn/server/ta.key)
</tls-auth>
EOF
echo "Generated $OVPN_DIR/$CLIENT.ovpn"
Usage: sudo bash gen-client.sh client1
OpenVPN vs WireGuard Comparison
| Feature | OpenVPN | WireGuard |
|---|---|---|
| Protocol | TLS over UDP/TCP | UDP only (WireGuard protocol) |
| Port flexibility | UDP 1194 or TCP 443 | UDP only (default 51820) |
| Client support | All platforms + legacy devices | Modern platforms (Linux 5.6+, iOS, Android, Windows) |
| Configuration complexity | High (PKI, multiple files) | Low (key pair, single config) |
| Throughput | Good | Excellent (kernel-space) |
| Firewall traversal | Excellent (TCP 443 mode) | Limited (UDP only) |
| Audit history | Extensive (since 2001) | Lean, formally verified |
| Site-to-site support | Native | Native |
| Dynamic IPs | Via --float flag | Roams automatically |
| Best for | Broad compatibility, legacy clients, TCP-only networks | High performance, modern deployments, simplicity |
Recommendation: Choose WireGuard for new deployments where all clients run modern operating systems. Choose OpenVPN when you must support Windows 7/8, older iOS versions, or networks that block UDP entirely.
Real-World Scenario
You manage a small software development team where three remote developers access an internal GitLab instance and a staging database server. Your GitLab server lives on 192.168.10.10 inside your office network, behind a firewall that only exposes SSH and HTTPS to the internet.
After setting up OpenVPN with the steps above, each developer runs their dev-name.ovpn profile in the OpenVPN client. Once connected, their workstation receives an address in the 10.8.0.0/24 range. You add a static route on the OpenVPN server pushing 192.168.10.0/24 to clients:
push "route 192.168.10.0 255.255.255.0"
Now developers reach 192.168.10.10 directly over the encrypted tunnel without exposing GitLab to the public internet. You revoke access instantly by running ./easyrsa revoke dev-name followed by ./easyrsa gen-crl, then adding crl-verify /etc/openvpn/server/crl.pem to server.conf — no password changes required.
Gotchas and Edge Cases
Routing conflicts with 10.8.0.0/24: If your LAN already uses this subnet, change the server directive to an unused range like 10.9.0.0 255.255.255.0 and update the NAT masquerade rule accordingly.
Split tunneling vs full tunnel: The redirect-gateway def1 push directive routes all client traffic through the VPN. For split tunneling (only specific subnets), remove that line and push explicit routes instead.
TCP mode for restrictive firewalls: If UDP 1194 is blocked, change proto udp to proto tcp and port 1194 to port 443 in both server and client configs. Performance will be lower due to TCP-over-TCP inefficiency, but traversal improves dramatically.
Certificate revocation: Always generate and serve a CRL when revoking clients. Without crl-verify in server.conf, revoked certs continue to work.
MTU fragmentation: If clients experience slow throughput or dropped packets, add tun-mtu 1400 and mssfix 1360 to server.conf.
Persistent IP assignments: Use a client-config-dir (CCD) with per-client ifconfig-push directives to assign the same IP each time a client connects.
Troubleshooting
Service fails to start: Run sudo journalctl -u openvpn-server@server -e to read the full log. Common causes are wrong file paths in server.conf or mismatched ta.key direction.
Clients connect but cannot reach the internet: Confirm IP forwarding is active (cat /proc/sys/net/ipv4/ip_forward should return 1), that the MASQUERADE rule is present (sudo iptables -t nat -L), and that DEFAULT_FORWARD_POLICY=ACCEPT is set in /etc/default/ufw.
TLS handshake failed: Ensure the key-direction value matches: 0 on the server side, 1 on the client side (or use tls-crypt instead of tls-auth which is direction-agnostic).
DNS leaks: Verify that the dhcp-option DNS pushes are received by the client with openvpn --show-status client.log. Use dnsleak.com to confirm DNS queries exit through the VPN.
Client certificate expired: Easy-RSA defaults to 825-day client certs. Regenerate before expiry with ./easyrsa renew client1 nopass and redistribute the updated .ovpn file.
Summary
- Install
openvpnandeasy-rsa, then initialize a PKI with a CA, server certificate, DH parameters, andta.key - Write
server.confwith AES-256-GCM cipher, TLS auth hardening, and the10.8.0.0/24VPN subnet - Enable IP forwarding and add a UFW MASQUERADE rule so VPN clients can reach the internet
- Use a shell script to embed certificates inline into self-contained
.ovpnclient profiles - OpenVPN is the right choice for legacy clients and TCP-only firewall traversal; WireGuard wins on performance and simplicity for modern deployments
- Revoke clients instantly via Easy-RSA CRL without resetting shared secrets
- Monitor
/var/log/openvpn/openvpn.logand usejournalctlfor service-level diagnostics