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 ufw if 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

FeatureOpenVPNWireGuard
ProtocolTLS over UDP/TCPUDP only (WireGuard protocol)
Port flexibilityUDP 1194 or TCP 443UDP only (default 51820)
Client supportAll platforms + legacy devicesModern platforms (Linux 5.6+, iOS, Android, Windows)
Configuration complexityHigh (PKI, multiple files)Low (key pair, single config)
ThroughputGoodExcellent (kernel-space)
Firewall traversalExcellent (TCP 443 mode)Limited (UDP only)
Audit historyExtensive (since 2001)Lean, formally verified
Site-to-site supportNativeNative
Dynamic IPsVia --float flagRoams automatically
Best forBroad compatibility, legacy clients, TCP-only networksHigh 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 openvpn and easy-rsa, then initialize a PKI with a CA, server certificate, DH parameters, and ta.key
  • Write server.conf with AES-256-GCM cipher, TLS auth hardening, and the 10.8.0.0/24 VPN 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 .ovpn client 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.log and use journalctl for service-level diagnostics