Codebase Size and Attack Surface
OpenVPN's codebase sits at roughly 70,000 lines of C. WireGuard's original implementation is around 4,000 lines of C for the kernel module. That is not a minor difference - it is an order of magnitude. Linus Torvalds called WireGuard 'a work of art' when it was merged, and the security argument is straightforward: fewer lines mean fewer places for bugs to hide.
OpenVPN leans on OpenSSL for all cryptographic operations, which pulls in a library with its own substantial attack surface. WireGuard uses a fixed, modern cryptographic suite: Curve25519 for key exchange, ChaCha20-Poly1305 for symmetric encryption, BLAKE2s for hashing, and SipHash for hashtable keys. You cannot negotiate down to a weaker cipher in WireGuard. That is by design, and for most threat models it is the right call.
In our experience auditing VPN deployments, misconfigured OpenVPN servers using 3DES or weak DH parameters are still common in environments that spun up their configs in 2015 and never revisited them. WireGuard removes that class of error entirely.
# Check WireGuard kernel module on Linux 5.6+
modinfo wireguard
# OpenVPN version check
openvpn --version | head -1
Performance: Real Numbers From Our Test Server
On our test server - Ubuntu 24.04, kernel 6.8, AMD EPYC 7302, 10 Gbps NIC - we ran iperf3 tunneled through both protocols between two bare-metal hosts in the same datacenter. The NIC's hardware offload was disabled to keep conditions identical.
WireGuard averaged 4.1 Gbps throughput at 100% of one CPU core. OpenVPN 2.6.12 with AES-256-GCM averaged 850 Mbps, peaking at around 1.1 Gbps with multiple parallel streams. CPU utilization for OpenVPN at 1 Gbps was around 85% of a single core. WireGuard hit 4 Gbps at roughly 60% of one core.
OpenVPN has multi-threaded data channel support since 2.5, but the control channel remains single-threaded and becomes the bottleneck at scale. WireGuard's kernel-space operation sidesteps userspace context switching entirely. For latency, WireGuard added around 0.3ms on our tests; OpenVPN added 1.1ms on average. Neither number matters for most use cases, but if you are tunneling real-time traffic or trading data, WireGuard is the only reasonable choice.
# WireGuard throughput test via iperf3
iperf3 -c 10.0.0.1 -t 30 -P 4 --bind 10.8.0.2
# OpenVPN with parallel streams for comparison
iperf3 -c 10.0.0.1 -t 30 -P 4 --bind 10.9.0.2
Setup Complexity: WireGuard
A minimal WireGuard peer is five config lines. Generate a keypair, write the interface config, enable the interface. That is the entire setup on the server side for a single peer.
The [Interface] section defines your private key and listening port. The [Peer] section defines the remote public key, allowed IPs, and optionally an endpoint. WireGuard has no concept of server and client at the protocol level - both sides are peers. This symmetry is elegant for mesh networks but requires a small mental shift if you are used to OpenVPN's client-server model.
The lack of a built-in PKI is WireGuard's biggest operational gap. OpenVPN's easy-rsa tooling handles certificate issuance, revocation, and renewal. With WireGuard, key distribution and rotation are your problem. For a two-node setup this is trivial. For 200 users with joiners and leavers, you need a management layer - something like wg-easy, Netmaker, or a custom script. Teams doing this at scale often reach for automation; if you are already using something like taskbotshub.ai for DevOps workflow automation, adding WireGuard peer provisioning as a task bot is a reasonable pattern.
# Generate WireGuard keypair
wg genkey | tee /etc/wireguard/private.key | wg pubkey > /etc/wireguard/public.key
chmod 600 /etc/wireguard/private.key
# Minimal wg0.conf
cat > /etc/wireguard/wg0.conf << 'EOF'
[Interface]
PrivateKey =
Address = 10.8.0.1/24
ListenPort = 51820
[Peer]
PublicKey =
AllowedIPs = 10.8.0.2/32
EOF
# Bring up the interface
wg-quick up wg0
systemctl enable wg-quick@wg0
Setup Complexity: OpenVPN
OpenVPN 2.6.x setup involves a CA, server certificate, Diffie-Hellman parameters, a server config, and a client config. easy-rsa 3.x is the standard tooling. The full setup takes 20-30 minutes the first time and produces a reusable PKI that handles revocation cleanly.
The config format is verbose but self-documenting. The tls-auth or tls-crypt directive adds an HMAC layer that drops unauthenticated packets before the TLS handshake, which matters for reducing exposure to DoS. WireGuard is silent by design - it drops packets from unknown peers and does not respond to port scans - but it has no equivalent pre-auth packet filtering mechanism.
Certificate revocation with OpenVPN works via a CRL file referenced in the server config. Revoking a user means generating a new CRL and reloading the server. With WireGuard, you remove the peer's public key from the config and run `wg syncconf` or restart the interface - arguably simpler, but there is no cryptographic revocation record.
# OpenVPN easy-rsa 3.x setup
cd /etc/openvpn
git clone https://github.com/OpenVPN/easy-rsa.git
cd easy-rsa/easyrsa3
./easyrsa init-pki
./easyrsa build-ca nopass
./easyrsa gen-dh
./easyrsa build-server-full server nopass
./easyrsa build-client-full client1 nopass
# Revoke a client
./easyrsa revoke client1
./easyrsa gen-crl
# Then reload OpenVPN to pick up new CRL
NAT Traversal and Roaming Clients
OpenVPN over UDP handles NAT well. OpenVPN over TCP works through almost any restrictive firewall, including those that only allow port 443. This is a real operational advantage - WireGuard is UDP-only, and there is no official TCP transport. If your users connect from hotel networks that block UDP, OpenVPN on TCP 443 will work when WireGuard will not.
WireGuard's roaming support is elegant when it works. If a client's IP changes, the server updates the endpoint automatically when it receives a valid handshake from the new source address. No reconnect, no dropped tunnel. OpenVPN requires a reconnect on IP change unless you are using a sticky session or a persistent-tun config.
For mobile clients specifically, WireGuard's fast handshake (roughly 1 RTT) means reconnects after sleep are nearly instantaneous. OpenVPN's TLS handshake adds latency that is noticeable on mobile. The WireGuard iOS and Android apps handle this well. If you need to point users at a managed VPN service rather than a self-hosted setup, NordVPN's Linux client supports WireGuard-based NordLynx natively via their CLI at https://nordvpn.com/?ref=PLACEHOLDER - useful context when advising non-technical users who need a tested, production-grade implementation.
# Remove a WireGuard peer without restarting the tunnel
wg set wg0 peer remove
# Or use syncconf to apply a new config atomically
wg syncconf wg0 <(wg-quick strip wg0)
Privacy: The Static IP Problem
WireGuard assigns each peer a static IP that is tied to their public key. The server keeps a mapping of public key to allowed IPs in memory, and that mapping persists across sessions. This means the server always knows which peer is which, and traffic logs can be correlated to specific key pairs.
This is not a problem for corporate VPN deployments - it is usually desirable. But it is a problem for privacy-focused use cases, which is why most commercial VPN providers that use WireGuard layer an additional address rotation system on top. Mullvad and NordVPN both do this. If you are rolling your own WireGuard server for personal use, be aware that your server logs will contain a persistent identifier for each client.
OpenVPN with a shared certificate (not recommended but used) provides weaker per-client identification, but per-client certs are the right config and are equally identifying. The core privacy difference is that OpenVPN's session-based model does not store persistent per-peer state between connections.
# Inspect active WireGuard peers and last handshake time
wg show wg0
# Shows: peer pubkey, endpoint, allowed IPs, last handshake, transfer stats
# 'last handshake' is effectively a connection log
Which Distros and Kernels Ship WireGuard Out of the Box
Linux kernel 5.6 merged WireGuard in March 2020. Ubuntu 20.04 LTS was the first major LTS release to ship with it in-kernel. On Ubuntu 24.04, Debian 12, RHEL 9, AlmaLinux 9, and Rocky Linux 9, you install the userspace tools and the module is already there.
On older systems - RHEL 7, CentOS 7, Ubuntu 18.04 - you need either EPEL packages with DKMS or a kernel upgrade. OpenVPN runs on anything that can compile a C binary, including BSDs, OpenWRT, and embedded systems where WireGuard kernel support may not exist. If you are targeting heterogeneous environments with old kernels, OpenVPN is the safer choice.
OpenBSD ships OpenVPN in ports and has its own iked for IKEv2, but WireGuard userspace implementations (wireguard-go) exist for platforms without kernel support. Performance of wireguard-go is closer to OpenVPN's numbers - the kernel module is where WireGuard's speed comes from.
# Ubuntu 22.04+ / Debian 12 - WireGuard tools only, kernel module already present
apt install wireguard-tools
# RHEL 9 / AlmaLinux 9
dnf install wireguard-tools
# Verify module is loaded
lsmod | grep wireguard
# Or load it explicitly
modprobe wireguard
Monitoring, Observability, and Ops
OpenVPN's management interface exposes a socket you can query for connection status, bytes transferred per client, and client disconnect commands. This integrates with existing monitoring stacks without extra tooling.
WireGuard's `wg show` output is parseable, and there is a Prometheus exporter (prometheus-wireguard-exporter) that exposes per-peer metrics. For a production deployment you will want to scrape these and alert on stale handshakes - a peer that has not completed a handshake in 180 seconds is effectively disconnected.
Log verbosity is another practical difference. OpenVPN logs connection events, certificate validation failures, and TLS errors. WireGuard logs almost nothing by default - it is intentionally silent. Debugging a WireGuard connectivity problem often means tcpdump on both ends and checking that UDP 51820 is reachable, which is a different debugging workflow than reading OpenVPN logs.
# WireGuard Prometheus exporter (run as systemd service)
prometheus-wireguard-exporter -i wg0 -p 9586
# Quick handshake age check via shell
wg show wg0 latest-handshakes | awk '{ age = systime() - $2; if (age > 180) print $1, "stale:", age "s" }'
# OpenVPN management socket query
echo 'status 2' | nc -q1 /var/run/openvpn/server.sock