Set Up an OpenVPN Server
Build a complete OpenVPN server on Linux using Easy-RSA 3 for PKI, a hardened server.conf, client .ovpn profiles, and systemd — with a WireGuard comparison.
Before you start
- ▸A Linux server with a public IP address and root or sudo access
- ▸UDP port 1194 open in any upstream firewall or cloud security group
- ▸Basic familiarity with systemd service management and firewall concepts
- ▸OpenVPN 2.4 or later (for tls-crypt and AES-256-GCM support)
OpenVPN remains one of the most widely deployed VPN solutions on Linux — battle-tested, audited, and supported everywhere from home routers to enterprise firewalls. Setting it up from scratch involves three moving parts: a small PKI (handled by Easy-RSA), a server config, and per-client credential bundles. This guide walks through all three on a single server, then touches on when WireGuard is the better call.
Install OpenVPN and Easy-RSA
Debian / Ubuntu
sudo apt update
sudo apt install openvpn easy-rsa
Fedora / RHEL 9 / Rocky Linux
sudo dnf install openvpn easy-rsa
Arch Linux
sudo pacman -S openvpn easy-rsa
Build the PKI with Easy-RSA 3
Easy-RSA 3 (the current generation) ships its own easyrsa script and keeps all state in a self-contained directory. Never use Easy-RSA 2; it is unmaintained and insecure by today's standards.
Initialise the PKI directory
mkdir -p ~/openvpn-ca
cp -r /usr/share/easy-rsa/* ~/openvpn-ca/
cd ~/openvpn-ca
./easyrsa init-pki
Create the Certificate Authority
./easyrsa build-ca nopass
You will be prompted for a Common Name (e.g. MyVPN-CA). The nopass flag skips a CA key passphrase — convenient for automation, but for production consider protecting the CA key with a strong passphrase and keeping it offline.
Generate the server certificate and key
./easyrsa gen-req server nopass
./easyrsa sign-req server server
Type yes to confirm signing. You now have pki/issued/server.crt and pki/private/server.key.
Generate Diffie-Hellman parameters and a tls-crypt key
./easyrsa gen-dh
openvpn --genkey secret ~/openvpn-ca/pki/private/ta.key
DH parameter generation can take a few minutes. The ta.key is used for tls-crypt, which authenticates and encrypts the TLS handshake, preventing port scans from identifying your server as OpenVPN and mitigating some DoS vectors.
Issue a client certificate
./easyrsa gen-req client1 nopass
./easyrsa sign-req client client1
Repeat with a different name for each additional client. Never reuse a certificate across multiple devices.
Install PKI Files on the Server
sudo install -o root -g root -m 600 \
~/openvpn-ca/pki/ca.crt \
~/openvpn-ca/pki/issued/server.crt \
~/openvpn-ca/pki/private/server.key \
~/openvpn-ca/pki/dh.pem \
~/openvpn-ca/pki/private/ta.key \
/etc/openvpn/server/
Write the Server Configuration
sudo tee /etc/openvpn/server/server.conf <<'EOF'
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
tls-crypt /etc/openvpn/server/ta.key
server 10.8.0.0 255.255.255.0
ifconfig-pool-persist /var/lib/openvpn/ipp.txt
push "redirect-gateway def1 bypass-dhcp"
push "dhcp-option DNS 9.9.9.9"
push "dhcp-option DNS 149.112.112.112"
keepalive 10 120
cipher AES-256-GCM
tls-version-min 1.2
tls-cipher TLS-ECDHE-RSA-WITH-AES-256-GCM-SHA384
auth SHA256
user nobody
group nogroup
persist-key
persist-tun
status /var/log/openvpn/status.log
log-append /var/log/openvpn/openvpn.log
verb 3
EOF
Key decisions above: UDP port 1194 is standard and faster than TCP for VPN tunnels. AES-256-GCM with TLS 1.2 minimum is the modern baseline; older AES-CBC ciphers are still functional but offer no AEAD protection. If your clients are OpenVPN 2.5+, you can also set data-ciphers AES-256-GCM:AES-128-GCM for negotiated cipher selection.
sudo mkdir -p /var/log/openvpn /var/lib/openvpn
Enable IP Forwarding and Firewall Rules
Enable kernel forwarding persistently
sudo tee /etc/sysctl.d/99-openvpn.conf <<'EOF'
net.ipv4.ip_forward = 1
EOF
sudo sysctl --system
Firewall — nftables (universal approach)
sudo nft add table ip nat
sudo nft add chain ip nat postrouting { type nat hook postrouting priority 100 \; }
# Replace eth0 with your actual outbound interface
sudo nft add rule ip nat postrouting oifname "eth0" masquerade
Firewall — firewalld (Fedora / RHEL / Rocky)
sudo firewall-cmd --permanent --add-service=openvpn
sudo firewall-cmd --permanent --add-masquerade
sudo firewall-cmd --reload
Firewall — ufw (Ubuntu / Debian)
sudo ufw allow 1194/udp
# In /etc/ufw/before.rules, add a MASQUERADE rule above *filter
# Then:
sudo ufw reload
With ufw you must also edit /etc/default/ufw and set DEFAULT_FORWARD_POLICY="ACCEPT".
Start and Enable the OpenVPN Service
sudo systemctl enable --now openvpn-server@server
The systemd unit name follows the pattern openvpn-server@<config-name> where <config-name> matches the filename without .conf.
Create a Client Profile (.ovpn)
An .ovpn file embeds all certificates and keys inline, making distribution straightforward. Replace YOUR_SERVER_IP with the public IP or hostname of your server.
CLIENT=client1
SERVER_IP=YOUR_SERVER_IP
CA_DIR=~/openvpn-ca/pki
cat > ~/${CLIENT}.ovpn <<EOF
client
dev tun
proto udp
remote ${SERVER_IP} 1194
resolv-retry infinite
nobind
persist-key
persist-tun
remote-cert-tls server
cipher AES-256-GCM
auth SHA256
verb 3
<ca>
$(cat ${CA_DIR}/ca.crt)
</ca>
<cert>
$(cat ${CA_DIR}/issued/${CLIENT}.crt)
</cert>
<key>
$(cat ${CA_DIR}/private/${CLIENT}.key)
</key>
<tls-crypt>
$(cat ${CA_DIR}/private/ta.key)
</tls-crypt>
EOF
Transfer the .ovpn file to the client over a secure channel (SCP, encrypted email, or a one-time-link service). It contains private key material — treat it accordingly.
Verify the Server Is Running
sudo systemctl status openvpn-server@server
sudo journalctl -u openvpn-server@server -n 50
ip addr show tun0
You should see a tun0 interface with address 10.8.0.1. On the client side, after importing the profile, the client will receive an address in the 10.8.0.0/24 range and all traffic will route through the tunnel if redirect-gateway is active.
Troubleshooting
- TLS handshake failed / AUTH_FAILED — Confirm the
ta.keyis identical on both server and client, and that both usetls-crypt(nottls-auth). - No internet through the tunnel — Verify
net.ipv4.ip_forward=1is active (sysctl net.ipv4.ip_forward) and that the masquerade/NAT rule is applied to the correct outbound interface. - Connection times out on port 1194 — Check your cloud provider's security group or host firewall. UDP is often blocked by default on cloud VMs.
- Certificate verify failed — The server cert must be signed with
sign-req server(notclient). Theremote-cert-tls serverclient directive enforces this. - Unit not found / service fails to start — On RHEL/Rocky, the group
nogroupmay not exist; usenobodyfor bothuserandgroup, or create the group first.
Modern Alternative: WireGuard
If you are starting a new deployment rather than integrating with existing OpenVPN infrastructure, seriously evaluate WireGuard. It is built into the Linux kernel since 5.6, has a dramatically smaller codebase (~4 000 lines vs OpenVPN's ~100 000+), is faster on modern hardware, and its configuration is considerably simpler — no PKI ceremony, just key pairs and [Peer] blocks in /etc/wireguard/wg0.conf.
OpenVPN still wins when you need: certificate revocation at scale, deep firewall traversal (TCP 443 mode), legacy client support, or fine-grained access control via certificates. For a lean personal VPN or a small team, WireGuard is the pragmatic modern choice.
Frequently asked questions
- Can I run OpenVPN on TCP instead of UDP?
- Yes — change 'proto udp' to 'proto tcp' in both server.conf and the client profile. TCP mode is useful for traversing restrictive firewalls, especially on port 443, but adds overhead from double-acknowledgement. Use it only when UDP is blocked.
- How do I revoke a client certificate?
- Run './easyrsa revoke client1' followed by './easyrsa gen-crl' in your PKI directory. Copy the generated pki/crl.pem to /etc/openvpn/server/ and add 'crl-verify /etc/openvpn/server/crl.pem' to server.conf, then restart the service.
- What is the difference between tls-auth and tls-crypt?
- Both use a pre-shared key for HMAC authentication of the TLS channel, preventing unauthorised clients from reaching the OpenVPN handshake. tls-crypt (OpenVPN 2.4+) additionally encrypts the control channel, hiding that the port is running OpenVPN at all. Prefer tls-crypt on modern deployments.
- Why does traffic not route through the tunnel even though I am connected?
- The two most common causes are: IP forwarding is not enabled on the server (check 'sysctl net.ipv4.ip_forward'), or the NAT/masquerade rule is missing or applied to the wrong interface. Confirm the outbound interface name with 'ip route' and recheck your firewall rules.
- Should I use OpenVPN or WireGuard for a new deployment?
- WireGuard is faster, simpler to configure, and kernel-native since Linux 5.6 — it is the better choice for most new personal or small-team VPNs. Stick with OpenVPN when you need certificate revocation at scale, TCP 443 firewall traversal, or compatibility with existing OpenVPN client ecosystems.
Related guides
Build a Mesh VPN with Nebula
Build a fully self-hosted mesh VPN with Nebula: create a CA, sign node certs, configure lighthouses, enforce group-based firewall rules, and run as a systemd service.
Common Linux Network Ports Reference
Learn Linux port ranges, read /etc/services, find what's listening with ss and nmap, and apply solid firewall rules to expose or block the right ports.
How to Configure a Static IP on Linux
Configure a static IP on Linux using Netplan, NetworkManager (nmcli), or systemd-networkd across Ubuntu, Fedora, Debian, and Arch with verified steps.
Expose a Service with Cloudflare Tunnel
Expose local services to the internet without port-forwarding using Cloudflare Tunnel. Install cloudflared, create a named tunnel, configure ingress rules, and run as a systemd service.