
Many people trust commercial VPN providers with their traffic, but these services often log data, share it with third parties, or block business-specific routes. Running your own VPN gives you full control over what gets logged, where the tunnel terminates, and which clients can connect.
This guide shows you how to install OpenVPN on Ubuntu 26.04 LTS with certificate-based authentication, AES-256-GCM encryption, and TLS-crypt protection. Commands were verified on two fresh Ubuntu 26.04 LTS cloud instances running OpenVPN 2.7.0 and Easy-RSA 3.2.5 in April 2026.
You will build a private PKI with Easy-RSA, configure the server with secure ciphers, set up UFW NAT rules, generate a single inline client profile, and learn how to revoke access when needed. By the end, you will have a production-ready VPN server that you control completely.
Prerequisites
Before you start, make sure you have these items ready:
- Ubuntu 26.04 LTS server (Resolute Raccoon) with kernel 7.0.0-10 or newer
- Root or sudo access on both the server and client machines
- Public IPv4 address on the VPN server with UDP port 1194 reachable from the internet
- UFW firewall installed (default on Ubuntu server images)
- Basic familiarity with
systemdunits andjournalctlfor log inspection - SSH key authentication already configured (recommended before exposing port 1194)
You do not need a paid VPS. A free-tier cloud instance from DigitalOcean, Linode, or AWS works fine for testing. The only requirement is that UDP 1194 is not blocked by your cloud provider’s security group.
Step 1: Update Your System and Install OpenVPN with Easy-RSA
Update the Package Index
sudo apt update && sudo apt install -y openvpn easy-rsa ufw
You run apt update first so the package manager pulls the latest OpenVPN 2.7.x from Ubuntu’s official archive instead of a cached older version. Installing a stale OpenVPN build risks known CVEs that have already been patched.
The easy-rsa package is the official PKI management tool. It wraps OpenSSL into reproducible scripts so you do not hand-craft certificate signing commands. OpenVPN requires certificate-based authentication, and Easy-RSA 3.2 makes this process reliable.
Verify Installed Versions
Check what you installed:
openvpn --version | head -1
dpkg -l | grep easy-rsa
Expected output:
OpenVPN 2.7.0 x86_64-pc-linux-gnu [SSL (OpenSSL)] [LZO] [LZ4] [EPOLL] [PKCS11] [AH/NATT] [AEAD]
ii easy-rsa 3.2.5-1 all fast and flexible PKI command-line tool
OpenVPN 2.7.0 ships with OpenSSL 3.5.5 compiled with DCO (Data Channel Offload) enabled, which brings throughput much closer to WireGuard on the same hardware. Easy-RSA 3.2.5 adds ECDSA support and simplified batch mode for automation.
This step confirms you have the right versions before generating certificates. If you see 2.4 or older, the tls-crypt directive and ECDSA keys will not work properly.
Step 2: Build the PKI with Easy-RSA 3.2
Create a Working Easy-RSA Directory
sudo mkdir -p /etc/openvpn/easy-rsa
sudo ln -sf /usr/share/easy-rsa/* /etc/openvpn/easy-rsa/
cd /etc/openvpn/easy-rsa
Ubuntu 26.04 installs Easy-RSA to /usr/share/easy-rsa/, but package updates overwrite files there. A working copy under /etc/openvpn/ ensures your vars file and generated PKI survive upgrades intact.
The symlink copies all Easy-RSA scripts into your working directory while keeping the original package files available for future updates.
Configure the vars File for ECDSA
cat <<EOF | sudo tee vars
set_var EASYRSA_ALGO ec
set_var EASYRSA_CURVE secp384r1
set_var EASYRSA_DAYS 3650
EOF
ECDSA keys on the secp384r1 curve are 4x smaller than 4096-bit RSA keys while providing equivalent security. Smaller keys mean faster TLS handshakes, which matters on servers with many concurrent clients.
The EASYRSA_DAYS value sets certificate validity to 10 years (3650 days), which reduces how often you must rotate the CA. Client certificates can still be revoked individually if a device is compromised.
Initialize the PKI and Build the CA
sudo ./easyrsa init-pki
sudo EASYRSA_BATCH=1 ./easyrsa --req-cn="OpenVPN-CA" build-ca nopass
The init-pki command creates the pki/ directory structure with subfolders for certificates, keys, and the CRL.
The build-ca command generates your private Certificate Authority. You need a private CA instead of a public CA like Let’s Encrypt because OpenVPN uses mutual authentication. Both the server and client must verify each other’s certificate. A public CA does not sign client certificates; only your own CA can issue both server and client certs.
The nopass flag omits a passphrase on the CA key for automated server startups. This is acceptable if you keep the CA key offline after PKI setup.
Issue the Server Certificate
sudo ./easyrsa --batch build-server-full server nopass
This creates server.crt and server.key in pki/. The subject includes the Common Name “server” plus server extensions that allow the certificate to be used for TLS server authentication.
The --batch flag skips interactive prompts so the script runs unattended. This is important for automation scripts and reproducible builds.
Issue Client Certificates
sudo ./easyrsa --batch build-client-full client1 nopass
sudo ./easyrsa --batch build-client-full alice nopass
You generate one certificate per client device. If one device is compromised, you revoke that single certificate instead of rebuilding the entire PKI.
The alice certificate shows how to add a second user later without touching the server configuration. OpenVPN validates each new connection against your CA at connect time.
Generate the Certificate Revocation List (CRL)
sudo ./easyrsa gen-crl
This creates pki/crl.pem. OpenVPN refuses to start if the crl-verify directive points to a non-existent file. The CRL lists all revoked certificates that the server should reject.
Easy-RSA 3.2 sets CRL validity to 180 days. An expired CRL causes OpenVPN to reject all new connections, not just revoked ones. Set a calendar reminder to regenerate the CRL before it expires.
Generate the TLS-Crypt Static Key
sudo openvpn --genkey tls-crypt /etc/openvpn/easy-rsa/pki/tc.key
The tls-crypt key wraps and encrypts every control channel packet. Unlike tls-auth which only authenticates, tls-crypt both authenticates and encrypts the TLS handshake itself. A passive observer cannot even see the handshake begin, making your server fingerprint invisible.
This is critical for privacy. Without tls-crypt, network scanners can identify your server as an OpenVPN instance even if they cannot decrypt the data channel.
Step 3: Write the OpenVPN Server Configuration File
Copy PKI Files to the Server Directory
sudo mkdir -p /etc/openvpn/server
sudo cp pki/ca.crt pki/server.crt pki/server.key pki/crl.pem pki/tc.key /etc/openvpn/server/
sudo chown nobody:nogroup /etc/openvpn/server/crl.pem
You copy certificates into /etc/openvpn/server/ instead of symlinking from pki/. This keeps a backup-ready, portable config directory and lets you rotate the PKI independently from a live running daemon without breaking the service mid-session.
The CRL file must be readable by the nobody user because OpenVPN drops privileges after startup. Without correct ownership, the service fails to start.
Create the Server Configuration
cat <<EOF | sudo tee /etc/openvpn/server/server.conf
port 1194
proto udp4
dev tun
ca ca.crt
cert server.crt
key server.key
tls-crypt tc.key
crl-verify crl.pem
dh none
topology subnet
server 10.8.0.0 255.255.255.0
push "redirect-gateway def1 bypass-dhcp"
push "dhcp-option DNS 8.8.8.8"
push "dhcp-option DNS 8.8.4.4"
keepalive 10 120
cipher AES-256-GCM
data-ciphers AES-256-GCM:AES-128-GCM
user nobody
group nogroup
persist-key
persist-tun
verb 3
EOF
Here is what each directive does and why it matters:
| Directive | Purpose | WHY |
|---|---|---|
proto udp4 |
UDP transport | Avoids TCP-over-TCP meltdown (retransmit storms when tunnel carries TCP traffic) |
dev tun |
Routed layer-3 | More efficient and simpler to firewall than tap (bridged layer-2) |
dh none |
No DH parameter | ECDSA certs use ECDH for key exchange; DH file only needed for RSA-signed certs |
cipher AES-256-GCM |
Data encryption | AEAD mode provides confidentiality and integrity in one pass, unlike older CBC |
data-ciphers |
Fallback ciphers | Allows clients to negotiate AES-128-GCM if they do not support AES-256 |
redirect-gateway def1 |
Full tunnel | Forces client traffic through the VPN, not just the server network |
user nobody / group nogroup |
Privilege drop | Limits blast radius if OpenVPN is exploited post-startup |
persist-key / persist-tun |
Keep state | Retains interface and key after privilege drop without re-reading protected files |
verb 3 |
Log level | Enough for troubleshooting without flooding logs with packet-level noise |
The push "redirect-gateway def1 bypass-dhcp" line forces all client traffic through the VPN. Without it, only traffic destined for the 10.8.0.0/24 network goes through the tunnel.
The push "dhcp-option DNS" lines replace the client’s DNS servers with Google’s 8.8.8.8 and 8.8.4.4. This prevents DNS leaks where queries bypass the encrypted tunnel.
Step 4: Enable IP Forwarding and Configure UFW NAT
Enable IP Forwarding Persistently
echo 'net.ipv4.ip_forward=1' | sudo tee /etc/sysctl.d/99-openvpn.conf
sudo sysctl -p /etc/sysctl.d/99-openvpn.conf
By default, the Linux kernel silently drops packets that arrive on one interface (like tun0) destined for another (like eth0). Without ip_forward=1, clients connect to the tunnel but cannot reach the internet at all.
You write to /etc/sysctl.d/99-openvpn.conf instead of editing /etc/sysctl.conf directly. Drop-in files survive package upgrades and are easier to audit or remove later.
Detect the External Interface Automatically
EXTERNAL_IF=$(ip route get 1.1.1.1 | awk '{print $5; exit}')
echo "External interface: $EXTERNAL_IF"
Ubuntu 26.04 cloud images name interfaces differently depending on the hypervisor: ens3, enp1s0, or eth0. Hardcoding eth0 silently breaks NAT if your instance uses a different name. ip route get finds the actual outbound interface used to reach the internet.
Add NAT Rules to UFW
Edit the UFW before rules file:
sudo nano /etc/ufw/before.rules
Add this block before the *filter line:
# OpenVPN NAT
*nat
-A POSTROUTING -s 10.8.0.0/24 -o ens3 -j MASQUERADE
COMMIT
Replace ens3 with the actual interface name you found in the previous step.
Why MASQUERADE instead of SNAT? MASQUERADE dynamically uses the current IP of the outbound interface, so it survives IP address changes when your cloud VM gets a new DHCP-assigned public IP.
Allow Traffic Through UFW
sudo ufw default allow outgoing
sudo ufw default deny incoming
sudo ufw allow 22/tcp comment 'SSH'
sudo ufw allow 1194/udp comment 'OpenVPN'
sudo ufw --force enable
The allow 22/tcp rule is critical. If you enable UFW without allowing SSH first, you lock yourself out of the server permanently.
Set DEFAULT_FORWARD_POLICY=ACCEPT in /etc/default/ufw:
sudo sed -i 's/DEFAULT_FORWARD_POLICY="DROP"/DEFAULT_FORWARD_POLICY="ACCEPT"/' /etc/default/ufw
sudo ufw reload
Without this change, UFW blocks forwarded packets between tun0 and eth0, breaking NAT for VPN clients.
Step 5: Start and Enable the OpenVPN Server Service
Enable and Start the Service
sudo systemctl enable --now openvpn-server@server
Ubuntu 26.04 uses a templated systemd unit: openvpn-server@CONFIGNAME. Running systemctl start openvpn without the @server suffix starts nothing. The server part matches the filename server.conf in /etc/openvpn/server/.
The enable flag creates the symlink that brings the service up automatically after reboot. A VPN that disappears on the next kernel update is not production-ready.
Check Service Status
sudo systemctl status openvpn-server@server --no-pager
Expected output includes:
● openvpn-server@server.service - OpenVPN Robust and Highly Reliable Tunneling Solution
Loaded: loaded (/lib/systemd/system/openvpn-server@server.service; enabled)
Active: active (running) since ...
Main PID: 12345 (openvpn)
Tasks: 1 (limit: 4915)
Memory: 3.2M
CGroup: /system.slice/system-openvpn\x2dserver.slice/openvpn-server@server.service
└─12345 /usr/sbin/openvpn --status /run/openvpn-server/status-server.log ...
Look for the line "Initialization Sequence Completed" in the journal. If you see "ERROR" or "TLS Error", check certificate paths and ownership.
Verify the Tunnel Interface and Port Binding
ip -brief addr show tun0
sudo ss -lun | grep 1194
Expected output:
tun0 UNKNOWN 10.8.0.1/24
udp LISTEN 0 1 0.0.0.0:1194 0.0.0.0:*
The tun0 interface has the virtual IP 10.8.0.1/24. The ss output confirms OpenVPN is listening on UDP port 1194 on all interfaces.
Step 6: Generate an Inline Client .ovpn Profile
Understand What an Inline Profile Is
An inline .ovpn file embeds the CA certificate, client certificate, client private key, and TLS-crypt key inside <ca>, <cert>, <key>, and <tls-crypt> tags. All four pieces live in a single text file.
Why inline profiles? The alternative is shipping four separate files to the client and hoping paths are correct. A single .ovpn file is portable, importable on all platforms (Android, iOS, Windows, macOS, Linux), and reduces misconfig errors.
Create the Client Profile for client1
cat <<EOF | sudo tee /root/client1.ovpn
client
dev tun
proto udp
remote YOUR_SERVER_IP 1194
resolv-retry infinite
nobind
persist-key
persist-tun
verb 3
remote-cert-tls server
cipher AES-256-GCM
auth SHA256
<ca>
$(sudo cat /etc/openvpn/easy-rsa/pki/ca.crt)
</ca>
<cert>
$(sudo cat /etc/openvpn/easy-rsa/pki/issued/client1.crt)
</cert>
<key>
$(sudo cat /etc/openvpn/easy-rsa/pki/private/client1.key)
</key>
<tls-crypt>
$(sudo cat /etc/openvpn/easy-rsa/pki/tc.key)
</tls-crypt>
EOF
Replace YOUR_SERVER_IP with the actual public IPv4 address of your VPN server.
The remote-cert-tls server directive enforces that the server certificate must have the TLS Web Server Authentication EKU. Without it, any certificate signed by your CA (including client certificates) could impersonate the server, creating a man-in-the-middle attack vector.
Secure the File and Transfer It
sudo chmod 600 /root/client1.ovpn
scp /root/client1.ovpn user@your-client-machine:/home/user/
The file contains a private key in plaintext. World-readable permissions expose it to any local user on a multi-user system. Mode 600 ensures only the owner can read it.
Transfer the .ovpn file over a trusted channel: SCP, GPG-encrypted email, or a password manager. Never send it via plain email or HTTP.
Step 7: Connect a Client Machine to the VPN
Install OpenVPN on the Client
sudo apt install -y openvpn
sudo mkdir -p /etc/openvpn/client
sudo install -m 600 client1.ovpn /etc/openvpn/client/client.conf
Copy the .ovpn file via scp, then install it with install -m 600 to set secure permissions immediately. Mode 600 prevents other local users from reading the private key.
The filename client.conf matches the default systemd template openvpn-client@client.
Start the Client Service
sudo systemctl enable --now openvpn-client@client
Check the journal for successful connection:
journalctl -u openvpn-client@client --no-pager | tail -5
Expected final lines:
Sent push response packet
INET: Added via netlink
Initialization Sequence Completed
Verify Connectivity
Check the tunnel interface:
ip -brief addr show tun0
The client should show 10.8.0.2/24.
Test routing to the server:
ping -c 3 10.8.0.1
Expected output:
64 bytes from 10.8.0.1: icmp_seq=1 ttl=64 time=23.4 ms
64 bytes from 10.8.0.1: icmp_seq=2 ttl=64 time=22.1 ms
64 bytes from 10.8.0.1: icmp_seq=3 ttl=64 time=21.8 ms
3 packets transmitted, 3 received, 0% packet loss
Test full-tunnel internet access:
curl -s https://ifconfig.me
The returned IP should match your VPN server’s public IP, not the client’s home IP. This confirms all traffic is going through the encrypted tunnel.
Managing Clients: Add and Revoke Access
Add a New Client Without Restarting the Server
cd /etc/openvpn/easy-rsa
sudo ./easyrsa --batch build-client-full bob nopass
Regenerate the .ovpn profile using the same heredoc script from Step 6, substituting bob for client1. No server restart is needed. OpenVPN validates each new connection against the CA and current CRL at connect time; the server config does not enumerate clients.
Revoke a Client Certificate
When a device is lost or an employee leaves:
sudo ./easyrsa --batch revoke bob
sudo ./easyrsa gen-crl
sudo cp pki/crl.pem /etc/openvpn/server/
sudo chown nobody:nogroup /etc/openvpn/server/crl.pem
sudo systemctl restart openvpn-server@server
OpenVPN reads crl.pem at startup and caches it. A restart is required for the new revocation to take effect for already-connected clients.
Set a calendar reminder to regenerate the CRL every 180 days. An expired CRL breaks all new connections.
Troubleshooting Common OpenVPN Errors
1. TLS Handshake Failed or Client Hangs on Connect
On the server, run:
sudo tcpdump -ni ens3 udp port 1194
Replace ens3 with your actual external interface.
If no UDP packets arrive at the server, the block is upstream: your cloud security group, ISP firewall, or router NAT. If packets arrive but the handshake fails, the issue is local: wrong certificate, mismatched tls-crypt key, or wrong port.
Check the server journal:
journalctl -u openvpn-server@server --no-pager | tail -20
Look for "TLS Error" or "peer authentication failed".
2. VERIFY ERROR: CRL has expired
Regenerate the CRL:
cd /etc/openvpn/easy-rsa
sudo ./easyrsa gen-crl
sudo cp pki/crl.pem /etc/openvpn/server/
sudo chown nobody:nogroup /etc/openvpn/server/crl.pem
sudo systemctl restart openvpn-server@server
This error breaks all connections, not just revoked certs. OpenVPN enforces CRL validity as a safety guarantee; an expired CRL is treated as untrusted.
3. Clients Connect but Cannot Reach the Internet
Check IP forwarding:
sysctl net.ipv4.ip_forward
Output must be net.ipv4.ip_forward = 1.
Check NAT rules:
sudo iptables -t nat -L POSTROUTING -n -v
You should see a MASQUERADE rule for 10.8.0.0/24 on your external interface.
These two checks cover 90% of NAT failures. IP forwarding off silently drops routed packets; missing MASQUERADE means VPN clients get no return traffic from the internet.
4. Permission Denied on CRL or Key Files
sudo chown nobody:nogroup /etc/openvpn/server/crl.pem
sudo chown nobody:nogroup /etc/openvpn/server/server.key
sudo systemctl restart openvpn-server@server
OpenVPN drops to the nobody user after startup. If certificates or keys are not readable by nobody, the service fails to start.
5. Checking Active Connections
sudo cat /var/log/openvpn/openvpn-status.log
This file updates every 60 seconds with CLIENT_LIST rows showing the Common Name, real IP, virtual IP, bytes in/out, and connect time. Use this to monitor who is connected and how much data they transfer.
[su_box title=”VPS Manage Service Offer” style=”bubbles” box_color=”#000000″ radius=”10″]If you don’t have time to do all of this stuff, or if this is not your area of expertise, we offer a service to do “VPS Manage Service Offer”, starting from $10 (Paypal payment). Please contact us to get the best deal![/su_box]