Wireguard for the Initiated

Wireguard is insanely great. Modern crypto, lightweight, in-kernel, UDP for everything and seamless roaming if your IP changes. What’s not to love?

But if you are just starting out with it and have a strong TCP/IP and Linux background, you might appreciate a concise explanation. This attempts to be that.


All it Does

Wireguard creates a virtual network interface, the first of which is called wg0, which encrypts and decrypts traffic. The kernel retains a list of peers and associated public keys. If the kernel get a packet destined to one of the peers, it gets encrypted and sent via UDP to the last known IP / port of that peer. The reverse happens if encrypted traffic arrives from a peer. If a properly encrypted packet arrives from an unexpected IP, that peer is updated and all response packets will now flow back to that new IP. That’s how roaming is supported — just like mosh. That’s all it does. Everything else — complicated IP subnetting, routing or any fancy port mappings— is done with netfilter, iproute2 or other standard kernel strategies. Wireguard’s elegance is in doing only one thing — encrypting and decrypting traffic — really well.


Get the package however you prefer. In Ubuntu you could:

Now you presumably have the kernel module and the related userland utilities wg and the helper application wg-quick.


Wireguard uses Curve25519 public / secret key pairs which can be generated with wg thusly:

We’ll do this on two machines, machine “A” and machine “B”. Let’s bring up a wireguard interface on machine “A” named wg0 with on it and pass it our secret key:

We’ll do the same using a different IP on machine “B”.

We can see what wireguard interface settings look like using wg with no arguments:


Now let’s tell machine “A” about the public key and initial IP / port combination to use to get to machine “B”:

And machine “B” about the same for “A”:

Peers are known by their public key. The associated IP / port combination can change as valid packets start to arrive from alternate addresses. Roaming clients are handled this way so you can just slap your laptop shut on one network and open it up on another without re-negotiating your VPN session.

The “allowed-ips” lets the interface know what network it can get to by encrypting packets with the given key and sending it to the given endpoint. Right now it is just the single machine but you could send all traffic or simply You can think about it like the route entry for this key / IP / port combination.

Now try pinging from and you should get responses:

Check on the peers your machine knows about with the wg command — you’ll see peers listed there now:

That’s it (Oh, one more thing!)

There is a helper app called wg-quick which reads config files from /etc/wireguard/ which makes setting things up a little easier. (and more painlessly survive reboots) Here’s a minimal example:

Now, fire up wg-quick and see if you get the same setup:

And wg-quick down wg0 will do the opposite. On Ubuntu, you might make that survive reboots with:


If you end up routing most or all traffic through a Wireguard tunnel, you might want to automatically drop the IP of a DNS cache in there and maybe set a search domain. You can do that fairly concisely with:

You’ll notice which is the DNS cache and example.com is how you specify your default search domain. Numbers are caches and names are search domains, all separated by commas.

Hub and Spoke

It is common to designate one peer as a “server” and have other clients peer through that to get to other peers. You do this using traditional iproute2 strategies (echo 1 > /proc/sys/net/ipv4/ip_forward and ip route entries) so there isn’t anything special here. I’ve been very happy with the minimal amount of CPU encrypting / decrypting to accomplish this costs. Even a $200 embedded appliance holds up fairly well at the center. You can see what I use for this in A Home Network which is a bit more broad article.


Wireguard is one of those “insanely great” things. It just does what it says on the can and nothing else. You can coax it to sending keepalive packets every once in a while (just add persistent-keepalive 25 to the wg command or PersistentKeepalive = 25 to the wg0.conf file to get a packet every 25 seconds) but beyond that it is totally silent unless traffic is requested. It handles roaming clients the way you want — just picks up where you left off if the packets are encrypted properly with the correct sequence number. And it doesn’t try to re-solve problems — all routing or interesting network configurations are handled the standard iproute2 way. It is the breath of fresh air you need if you are still stuck in IPSec / PPP / CHAP authentication hell.