The Problem with Unprotected Remote Access
Every time I set up infrastructure for a new project, the same question comes up: how do you let developers securely access internal services without exposing them to the public internet? SSH handles individual servers fine. But the moment you have a private database, an internal dashboard, or a staging environment, you need something that creates a real network tunnel — not just a one-off port forward.
OpenVPN is what I keep coming back to. It’s been around since 2001, runs on nearly everything, and holds up under conditions that would break a hastily configured alternative. Once it’s set up properly, connecting to a remote network feels no different from being plugged in locally. This guide walks through exactly that setup — from a bare Ubuntu 22.04 server to a working VPN with client configs ready to hand off.
Core Concepts Before You Touch the Terminal
A few things are worth understanding upfront. Skip this section and you’ll spend twice as long debugging later.
PKI: Why Certificates Matter
OpenVPN uses a Public Key Infrastructure (PKI) to authenticate both the server and each client. Every device that connects needs its own certificate — not just a username and password. The toolchain for managing this is called Easy-RSA, and it ships alongside OpenVPN.
The key players in your PKI:
- CA (Certificate Authority) — the root of trust. Signs everything else.
- Server certificate — proves to clients they’re connecting to the real server, not an impostor.
- Client certificates — proves to the server that the connecting device is actually authorized.
- Diffie-Hellman parameters — used for key exchange during the handshake.
- TLS Auth key (ta.key) — an extra HMAC layer that drops unsigned packets before they even reach OpenVPN’s processing stack.
Routing vs. Bridging
Two modes exist: routed (TUN) and bridged (TAP). For remote access, team VPNs, and self-hosted infrastructure, TUN is what you want. It creates a virtual network interface and routes traffic at Layer 3. TAP bridges at Layer 2 — rarely necessary, more complex to manage, and not covered here.
This guide uses TUN mode on port 1194 UDP — the OpenVPN standard.
Hands-on: Setting Up the OpenVPN Server
1. Install OpenVPN and Easy-RSA
Start with a clean Ubuntu 22.04 server. Update, then install both packages:
sudo apt update && sudo apt upgrade -y
sudo apt install openvpn easy-rsa -y
2. Build the PKI Infrastructure
Set up the Easy-RSA directory and initialize the PKI:
make-cadir ~/openvpn-ca
cd ~/openvpn-ca
./easyrsa init-pki
Build the Certificate Authority. You’ll be prompted for a CA name — something short like your org name works fine:
./easyrsa build-ca nopass
Generate the server certificate and key. The nopass flag skips a passphrase on the server key, which lets OpenVPN start automatically on boot:
./easyrsa gen-req server nopass
./easyrsa sign-req server server
Type yes when prompted to confirm. Then generate the Diffie-Hellman parameters — this step takes anywhere from 30 seconds to a couple of minutes depending on your hardware:
./easyrsa gen-dh
Finally, generate the TLS auth key:
openvpn --genkey secret ta.key
3. Copy Certificates to the OpenVPN Directory
sudo cp pki/ca.crt pki/issued/server.crt pki/private/server.key pki/dh.pem ta.key /etc/openvpn/server/
4. Configure the OpenVPN Server
OpenVPN ships with example configs. Copy the server template as your starting point:
sudo cp /usr/share/doc/openvpn/examples/sample-config-files/server.conf /etc/openvpn/server/server.conf
Open it for editing:
sudo nano /etc/openvpn/server/server.conf
Key settings to adjust:
# Point to your certificate files
ca ca.crt
cert server.crt
key server.key
dh dh.pem
# Enable TLS auth
tls-auth ta.key 0
# AES-256-CBC works on all clients; OpenVPN 2.5+ also accepts:
# data-ciphers AES-256-GCM:AES-128-GCM:CHACHA20-POLY1305
cipher AES-256-CBC
# Push DNS and a default route to clients
push "redirect-gateway def1 bypass-dhcp"
push "dhcp-option DNS 8.8.8.8"
push "dhcp-option DNS 8.8.4.4"
# Keepalive: ping every 10s, restart if no response for 120s
keepalive 10 120
# Drop root privileges after startup
user nobody
group nogroup
One note on the redirect-gateway directive: it routes all client traffic through the VPN. That’s usually what you want for security, but if you only need clients to reach internal resources, remove that line and push specific routes instead.
5. Enable IP Forwarding
Without this, VPN clients are stuck — they can reach the VPN server but nothing behind it. First thing I forgot on my initial setup. Don’t repeat that mistake:
echo 'net.ipv4.ip_forward=1' | sudo tee -a /etc/sysctl.conf
sudo sysctl -p
6. Configure Firewall and NAT
Find your primary network interface:
ip route | grep default
Set up NAT masquerading so VPN traffic can reach the internet (replace eth0 with your actual interface name):
sudo iptables -t nat -A POSTROUTING -s 10.8.0.0/24 -o eth0 -j MASQUERADE
sudo apt install iptables-persistent -y
sudo netfilter-persistent save
Then open the OpenVPN port and make sure SSH stays accessible:
sudo ufw allow 1194/udp
sudo ufw allow OpenSSH
sudo ufw enable
7. Start and Enable the Service
sudo systemctl start openvpn-server@server
sudo systemctl enable openvpn-server@server
sudo systemctl status openvpn-server@server
Look for active (running) in the status output. You should also see a tun0 interface come up:
ip addr show tun0
8. Generate a Client Certificate
Each connecting device needs its own certificate. Use descriptive names — you’ll thank yourself when revocation time comes:
cd ~/openvpn-ca
./easyrsa gen-req alice-laptop nopass
./easyrsa sign-req client alice-laptop
9. Create the Client Config File
The cleanest approach is an inline config — everything bundled into one .ovpn file. No certificate juggling for the end user:
client
dev tun
proto udp
remote YOUR_SERVER_IP 1194
resolv-retry infinite
nobind
persist-key
persist-tun
cipher AES-256-CBC
verb 3
key-direction 1
<ca>
# paste contents of ca.crt
</ca>
<cert>
# paste contents of alice-laptop.crt
</cert>
<key>
# paste contents of alice-laptop.key
</key>
<tls-auth>
# paste contents of ta.key
</tls-auth>
Doing this manually once is fine. For multiple clients, write a short bash script that reads each cert file and inlines it automatically. Hand someone a single .ovpn file and they’re connected in under a minute — no instructions needed.
A Few Things I’ve Learned the Hard Way
- Revoke certificates when a device is lost or an employee leaves. Easy-RSA covers this:
./easyrsa revoke alice-laptopfollowed by./easyrsa gen-crl. Addcrl-verify crl.pemto your server config so revoked certs are actually rejected — without that line, the CRL file is ignored. - Don’t reuse certificate names. Once a name is signed, reusing it causes certificate conflicts that are annoying to untangle. Stick with names like
alice-laptoporbob-phonefrom the start. - UDP beats TCP for VPN, almost always. OpenVPN over TCP creates a TCP-over-TCP situation — when the outer TCP layer retransmits, the inner TCP stack also retransmits, compounding packet loss instead of handling it. UDP sidesteps this entirely. Only fall back to TCP if you’re behind a firewall that blocks UDP 1194.
- Test with the terminal before importing into a GUI client. Run
openvpn --config alice-laptop.ovpndirectly. The terminal shows exact error messages. GUI clients swallow them silently, leaving you guessing.
Wrapping Up
A properly configured OpenVPN server gives you a stable, encrypted tunnel you can actually rely on — not one that drops when conditions wobble. The PKI setup looks daunting on first read. But strip it back and it’s just a CA signing certificates for both sides. Once that mental model clicks, the rest is just commands.
Next steps worth considering: automate client config generation with a bash script, enable the management interface to monitor active connections in real time, or hook into LDAP for centralized auth across your team. For most setups — especially those replacing a patchwork of SSH tunnels — what you’ve built here is already solid enough to ship.

