Ramblings of an aging IT geek
← Ramblings of an aging IT geek
networking

the vpn that fit in a screenful of config

Replacing a tangle of OpenVPN config with WireGuard, now that it has landed in the mainline kernel, and finding the whole thing simpler, faster, and easier to reason about.

Network cabling feeding into a rack

I have run OpenVPN to get back into my home network for years, and it worked, in the way that a car you have owned for a decade works: reliably, and only because you have learned to ignore the noises. The config had grown into a thicket of certificates, a CA I had to remember how to operate, TLS options I had copied from a forum in 2014 and never understood, and a connection that took a sociable few seconds to come up and dropped if my phone so much as glanced at a different network on the way out of the house.

WireGuard landed in the mainline Linux kernel earlier this year, with 5.6, and that was the nudge I needed. No more out-of-tree module to rebuild every kernel update. It is just there now. So I spent an evening replacing the whole OpenVPN arrangement, and the headline is that the new config is shorter than this paragraph.

The whole thing is two files

WireGuard's model is almost aggressively simple. There are no certificates and no certificate authority. Each peer, a server, a laptop, a phone, has a private key and a public key, exactly like SSH. You tell each side the other's public key and which IP addresses live behind it, and that is the entire trust model. If you have ever set up SSH keys, you already understand WireGuard.

The server side of mine is essentially this:

[Interface]
Address = 10.10.0.1/24
ListenPort = 51820
PrivateKey = <server private key>

[Peer]
# laptop
PublicKey = <laptop public key>
AllowedIPs = 10.10.0.2/32

[Peer]
# phone
PublicKey = <phone public key>
AllowedIPs = 10.10.0.3/32

And the laptop:

[Interface]
Address = 10.10.0.2/24
PrivateKey = <laptop private key>

[Peer]
PublicKey = <server public key>
Endpoint = home.example.org:51820
AllowedIPs = 10.10.0.0/24, 192.168.1.0/24
PersistentKeepalive = 25

That is genuinely the lot. Compare it to the OpenVPN setup it replaced, which had a server config, a client config, a CA key, a server cert, a client cert, a Diffie-Hellman parameter file I once waited an age to generate, and a TLS auth key. WireGuard reduced all of that to a key pair per device and a list of who may talk to whom.

A network rack and the box now terminating the tunnel

AllowedIPs is the clever bit, and the sharp bit

The field that does the most work is AllowedIPs, and it is worth understanding properly because it means two things at once. On outbound traffic it is a route: anything destined for those ranges goes down the tunnel. On inbound traffic it is an access control list: a packet is only accepted from a peer if its source address falls within that peer's AllowedIPs. One field, routing and a cryptographic source check together. It is elegant, and it will also catch you out if you set it carelessly, because a range you forgot to include simply will not route and you will spend twenty minutes wondering why one subnet is unreachable. It was me. It is always me.

On the laptop I have set it to cover both the VPN subnet and my home LAN, so I reach everything at home through the tunnel but normal browsing still goes out my local connection, a split tunnel, achieved by what I list rather than by any special flag. The PersistentKeepalive on the client matters when the server sits behind NAT or a stateful firewall, which mine does. It sends a tiny packet every 25 seconds to keep the path open so the connection survives being idle. Without it, an idle tunnel goes quiet and the firewall forgets the mapping.

The part that actually changed my behaviour

The config being short is pleasant. The thing that genuinely changed how I use it is roaming. WireGuard has no concept of a session that connects and disconnects. The interface is simply always there, and the moment there is a working path to the endpoint, traffic flows over it. There is no "connecting…" spinner because there is nothing to connect.

In practice that means my phone keeps the tunnel up as I walk out of the house and it hands off from wifi to mobile data. OpenVPN would notice the network change, drop, and make me wait while it laboriously re-established. WireGuard just carries on, because it is connectionless underneath. The IP it is tunnelling to has not changed, only the path my phone takes to reach it, and WireGuard does not care about the path. The first packet after the handover quietly re-establishes the encrypted flow and nothing above it notices. I genuinely forget the VPN is on now, which for a piece of network software is about the highest praise I have.

A couple of things worth knowing before you switch

Two practicalities, because the marketing simplicity hides a little setup. The first is that the server needs to forward and masquerade if you want peers to reach the wider home LAN, not just each other. That is one sysctl to enable IP forwarding and a single masquerade rule on the LAN-facing interface, the same plumbing any router needs, but WireGuard does not do it for you and the config files above say nothing about it. The wg-quick wrapper has PostUp and PostDown hooks where the firewall rules live, which keeps them tied to the interface coming up and down.

The second is key management at scale, such as scale is in a house. Adding a device means generating a key pair on it and adding its public key as a [Peer] on the server, then reloading. There is no enrolment, no signing, no expiry. That is wonderfully simple for a handful of devices and would become a chore for hundreds, at which point you would want tooling to manage the peer list. For me it is four devices and a text file, which is exactly the right amount of machinery. The phone, incidentally, takes the same config as a QR code, so onboarding it was pointing a camera at a screen rather than fiddling with imported certificates and a passphrase I would inevitably mistype.

It is faster too, measurably, because the crypto is modern and the code path is short and lives in the kernel rather than shuffling every packet up to a userspace daemon and back. But honestly the speed was never my complaint. My complaint was the ceremony, the certificate I dreaded ever having to reissue, the reconnect delay, the config I could no longer fully explain. WireGuard took all of that away and replaced it with a key pair and a list. I deleted the OpenVPN setup without a flicker of sentiment, which after all those years together feels slightly rude, but there it is.