I had an OpenVPN setup that I had built years ago, bodged a few times since, and quietly stopped understanding. It worked, mostly, in the way that a car with a strange noise still gets you to work. Every certificate renewal was an afternoon. Every new device was a config file emailed to myself and a fight with tls-auth. Last week I finally tore the whole thing out and replaced it with WireGuard, and the relief is real enough that I want to write down why, partly so the next person who is putting it off can read it and just go.
The headline is that WireGuard is small. The whole thing is a few thousand lines in the kernel, where OpenVPN is a sprawling userspace daemon with its own TLS stack and an options file that reads like a treaty. Small means I can hold it in my head, and "I can hold it in my head" is the single most underrated property a piece of infrastructure can have.
the config that made it click
The thing that finally made it make sense is that WireGuard has no concept of a client or a server. There are only peers, and each peer is a public key plus a list of which IP ranges sit behind it. That is the whole mental model. Once that landed, the config wrote itself.
Here is the "server", which is just the peer that happens to live on a box with a public address:
[Interface]
Address = 10.20.0.1/24
ListenPort = 51820
PrivateKey = <server private key>
[Peer]
# my laptop
PublicKey = <laptop public key>
AllowedIPs = 10.20.0.2/32
And the laptop:
[Interface]
Address = 10.20.0.2/24
PrivateKey = <laptop private key>
[Peer]
# the homelab
PublicKey = <server public key>
Endpoint = home.example.net:51820
AllowedIPs = 10.0.0.0/24, 10.20.0.0/24
PersistentKeepalive = 25
That is it. No certificate authority, no CRL, no Diffie-Hellman parameters to generate and worry about. AllowedIPs does double duty as both the routing table and the access control list, which takes a minute to get used to and then feels obviously correct. The PersistentKeepalive = 25 is the one non-obvious line: it sends a small packet every 25 seconds so the NAT on the laptop's side keeps the hole punched, which matters on mobile networks that close idle mappings aggressively.
the operational wins
The reconnection behaviour alone justifies the switch. WireGuard has no session in the OpenVPN sense. There is no handshake to renegotiate when you change networks, so moving from the office wifi to 4G to home is invisible. The tunnel does not "drop and reconnect", it simply carries on the moment packets can flow again, because the protocol is connectionless underneath. With OpenVPN, every network change was a visible stall while the TLS session was rebuilt. With WireGuard I genuinely cannot tell when my phone has switched networks, and that is the highest praise I can give a VPN.
Key management is the other one. No expiry. A WireGuard key does not have a lifetime, so there is no annual ritual of renewing certificates and discovering that the renewal script broke at some point in the intervening twelve months. Adding a device is two lines on each end and you are done. Removing one is deleting a peer block. The whole thing is small enough that I keep the configs in a git repo and the diffs are readable.
There are trade-offs, to be fair. WireGuard does not do dynamic IP assignment out of the box, so every peer gets a fixed address and you maintain that mapping yourself. For a homelab with a handful of devices that is a feature, not a burden. If you are running hundreds of clients you will want wg-quick's cousins or a management layer on top, and at that scale the tidy simplicity starts to need scaffolding again.
was it worth it
Yes, unreservedly. The migration took an evening, most of which was me double-checking that I understood the AllowedIPs routing before I trusted it. The OpenVPN config, its certificate authority, and the small museum of half-remembered options that came with it are all deleted now, and I do not miss any of it.
If you have an ageing OpenVPN setup that you keep meaning to replace, this is the nudge. It is less work than you think, and the thing you get at the end is small enough to actually understand. That last part is the whole point.