Setting up Wireguard

Wireguard is undoubtedly the go-to choice for a VPN. It's operationally simple, utilises state of the art cryptography, built into the Linux kernel, and evangelised by Linus himself1.

Using systemd, we can declaratively deploy Wireguard on almost any host by copy-and-pasting a small handful of files. Let's not dawdle.

The Wireguard Device

First, we prepare a Wireguard virtual network device.

/etc/systemd/network/99-wg0.netdev
[NetDev]
Name=wg0
Kind=wireguard
Description=WireGuard tunnel wg0

[WireGuard]
ListenPort=51820

Wireguard uses asymmetric cryptography to connect devices together. We could store a private key in the 99-wg0.netdev file in plain text, but that's not particularly secure.

Instead we can rely on systemd-creds to protect the private key. And by using a subshell (inside the <(...)) we don't reveal the private key at any point.

systemd-creds encrypt <(openssl rand -base64 32) /etc/credstore.encrypted/network.wireguard.private.99-wg0

The Wireguard Network

Next up is the Wireguard network. Address= is prechosen to relieve mental burden on yourself, the reader — you can change it if you prefer; but please, choose something from the allocated pool of private IPs: 192.168.0.0/16, 172.16.0.0/12, or 10.0.0.0/8. RFC 19182

/etc/systemd/network/99-wg0.network
[Match]
Name=wg0

[Network]
Address=172.16.0.1/12

Run systemctl restart systemd-networkd followed by wg to see your newly created virtual device.

$ wg
interface: wg0
  public key: (base64 key)
  private key: (hidden)
  listening port: 51820

Connecting a Peer

A VPN is no good without a devices connecting to it. So let's create a peer. A peer is simply a device in the network: a server, a laptop, a phone, etc.

It's quite impossible to give initial instructions here as the configuration method will depend on what device you're using as a peer. If you're using macOS, iOS, or iPadOS, the official Wireguard.app is great.

Regardless, it doesn't matter what device you choose because after you fill in the placeholders below, the configuration will look similar:

[Interface]
PrivateKey=(hidden)
Address=172.16.0.2/32
DNS=194.242.2.2

[Peer]
PublicKey=<copied from wg command>
AllowedIPs=0.0.0.0/0, ::/0
Endpoint=<server ip>:51820

Here's a pithy description of each setting:

Back over on the server, you'll need to register your new peer. We'll use a systemd drop-in so that we don't have to change any of the previous files.

/etc/systemd/network/99-wg0.netdev.d/peer.conf
[WireGuardPeer]
PublicKey=(peer public key)
AllowedIPs=172.16.0.2/32

Test the Network

Suprisingly, that's all you need. Enable Wireguard on your device and run wg on your server. You'll see something like below.

$ wg
interface: wg0
  public key: (base64 key)
  private key: (hidden)
  listening port: 51820

peer: (base64 key)
  endpoint: <peer ip>:52136
  allowed ips: 172.16.0.2/32
  latest handshake: 6 seconds ago
  transfer: 2.60 MiB received, 3.55 MiB sent

If you see a handshake and some transferred data, you've got yourself a successfully established connection.

Routing to the Internet

If you want your remote peers to connect to the internet via your server, you'll need to have IP forwarding enabled. Easy enough with a systemd drop-in for sysctl4:

/etc/sysctl.d/30-ipforward.conf
net.ipv4.ip_forward=1
net.ipv6.conf.default.forwarding=1
net.ipv6.conf.all.forwarding=1

Then enable.

sysctl --system

  1. https://lists.openwall.net/netdev/2018/08/02/124↩︎

  2. https://datatracker.ietf.org/doc/html/rfc1918↩︎

  3. https://mullvad.net↩︎

  4. https://wiki.archlinux.org/title/Sysctl↩︎