Elouworld

WireGuard (via systemd-networkd)

WireGuard is a new VPN protocol and software under development (although they are working for a stable release), using modern cryptography (ChaCha20, Ed25519…). It is simple to use and configure similarly to OpenSSH, you just need to share public keys between peers, compared to OpenVPN where you need to manage a private certificate authority (which has different advantages). Please note though that WireGuard is not made for having anonymous (not logged) clients unlike how OpenVPN can be used for, since it uses static IP addresses instead of DHCP (servers need to track the connections of clients). The main implementation lives in Linux kernel (in a kernel module) but other software implementations exist.

Please note that source code hasn’t yet been audited although formal verification has been done for some parts of WireGuard. The code base is way smaller than other related projects so it should be easier to audit and maintain.

This tutorial presents an installation on a Linux-based operating system with systemd (and systemd-networkd). Other setups are also possible but I found this one simple to use, although platform-dependant. Note that I am not interested by routing Internet traffic through the private network created, so this won’t be covered. I personnally keep using OpenVPN for that matter.

WireGuard installation

Here are instructions to install WireGuard on Debian and Arch Linux. You can find other operating system instructions on the official website.

On Debian

At the time of writing, WireGuard is shipped in Debian testing (bullseye) and unstable repositories. The wireguard meta package includes the kernel module and WireGuard tools. All dependencies are in stable repository, so this won’t cause issues. You can check the Package search website if it has changed.

Using instructions similar from Debian Wiki, let’s add the testing repository, but with a lower priority than default repositories to avoid conflicts. This is of course not required if you are already running Debian bullseye (11) or a more recent version (like unstable/sid).

1
2
echo "deb http://deb.debian.org/debian/ testing main" > /etc/apt/sources.list.d/testing.list
printf 'Package: *\nPin: release a=testing\nPin-Priority: 90\n' > /etc/apt/preferences.d/limit-testing

Then install the wireguard package. Note that this will install Linux kernel headers to build the WireGuard kernel module (dkms), unless a built kernel module is out now.

1
apt update && apt install wireguard

On Arch Linux

Using instructions from ArchWiki, install the wireguard-tools package for the tools and the appropriate package depending to your kernel:

  • wireguard-arch when using linux kernel package
  • wireguard-lts when using linux-lts kernel package
  • wireguard-dkms when using any other kernel (WireGuard kernel module will be built, so this requires Linux kernel headers, e.g. linux-mainline-headers if you are using linux-mainline kernel AUR package)
1
pacman -Syu wireguard-tools wireguard-arch

You may have to reboot to load the right kernel version and WireGuard module.

Configuration

You need to enable the systemd-networkd service, if not done already. The advantage is that it will manage both WireGuard setup and networking at the same time, easing the configuration. Please note that I haven’t tried using it alongside another networking daemon (like networking on Debian or dhcpcd on Arch Linux), so you may have to migrate your whole networking configuration and disable the other daemon.

On both server and client

You need to create a pair of keys on both server and client. Each client will need to get server’s public key and the server will need to get each client’s public key. Also a pre-shared key will be created, known to each peer.

1
2
3
4
5
6
7
8
9
10
11
# go to systemd-networkd configuration directory
cd /etc/systemd/network

# create files with right permissions (640) to prevent other system users to read secrets
umask 0027
touch wg0.netdev wg0.network wg-preshared.key wg-private.key wg-public.key
umask 0022
chown root:systemd-network *

# create a pair of keys
wg genkey | tee wg-private.key | wg pubkey > wg-public.key

wg0.netdev will be used for WireGuard configuration, wg0.network for networking configuration.

On systemd versions earlier than 242, wg0.netdev will contain secrets, on later versions it’s possible to reference other created files. At the time of writing, Debian uses systemd 241, which doesn’t support that feature.

On server

On the server, you need to create the pre-shared key (or on any other peer).

1
wg genpsk > wg-preshared.key

It needs to be copied to other peers.

Then here are sample server configuration files. I chose to use a subnet 10.213.213.0/24, you can also use IPv6 or even dual-stack.

Make sure to change <variable> to the appropriate key value. Less-than and greater-than signs (<>) must not be kept.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
cat <<EOF > wg0.netdev
[NetDev]
Name = wg0
Kind = wireguard
Description = wg server 10.213.213.0/24

[WireGuard]
# If running systemd >= 242
#PrivateKeyFile = /etc/systemd/network/wg-private.key
# If running systemd < 242
PrivateKey = <content of local wg-private.key>
ListenPort = 51820

# For any number of client:
[WireGuardPeer]
PublicKey = <content of client's wg-public.key>
AllowedIPs = 10.213.213.2/32
# If running systemd >= 242
#PresharedKeyFile = /etc/systemd/network/wg-preshared.key
# If running systemd < 242
PresharedKey = <content of wg-preshared.key>
EOF

AllowedIPs represents here the static IP address of the client. You need to choose one per client in the subnet.

You can change ListenPort to your needs (e.g. 53 to pass through firewalls, since it uses UDP).

1
2
3
4
5
6
7
8
9
10
11
cat <<EOF > wg0.network
[Match]
Name = wg0

[Network]
Address = 10.213.213.1/32

[Route]
Gateway = 10.213.213.1
Destination = 10.213.213.0/24
EOF

Then restart systemd-networkd:

1
systemctl restart systemd-networkd

On client

Like on the server, here are sample configuration files. Make sure you change the private IP address on each client, according to server configuration.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
cat <<EOF > wg0.netdev
[NetDev]
Name = wg0
Kind = wireguard
Description = wg client 10.213.213.2

[WireGuard]
# If running systemd >= 242
#PrivateKeyFile = /etc/systemd/network/wg-private.key
# If running systemd < 242
PrivateKey = <content of local wg-private.key>

[WireGuardPeer]
PublicKey = <server's public key>
AllowedIPs = 10.213.213.0/24
Endpoint = <server's public IP address>:51820
# If running systemd >= 242
#PresharedKeyFile = /etc/systemd/network/wg-preshared.key
# If running systemd < 242
PresharedKey = <content of wg-preshared.key>
PersistentKeepalive = 25
EOF

cat <<EOF > wg0.network
[Match]
Name = wg0

[Network]
Address = 10.213.213.2/32

[Route]
Gateway = 10.213.213.1
Destination = 10.213.213.0/24
GatewayOnlink = true
EOF

When ListenPort is not entered, a random port will be chosen by systemd, this is not important for clients that connect to the server specified with Endpoint.

Then restart systemd-networkd.

Check the connection

You can check that systemd-networkd enabled the WireGuard network interface (wg0) with the networkctl command:

1
2
3
4
5
$ networkctl
IDX LINK             TYPE               OPERATIONAL SETUP     
  1 lo               loopback           carrier     unmanaged 
  2 eno1             ether              routable    configured
  3 wg0              wireguard          routable    configured

You should now be able to ping server and client addresses from each other.

If there is any issue, check the logs with journalctl -eu systemd-networkd. If you have Unknown lvalue errors, it means you used unknown configuration directives, e.g. mistyped or from a more recent systemd version.

You can also get more information using the wg command:

On server:

1
2
3
4
5
6
7
8
9
10
11
12
$ wg
interface: wg0
  public key: <redacted>
  private key: (hidden)
  listening port: 51820

peer: redacted=
  preshared key: (hidden)
  endpoint: <client's public IP address>:44195
  allowed ips: 10.213.213.2/32
  latest handshake: 24 seconds ago
  transfer: 968.57 KiB received, 50.94 MiB sent

On client:

1
2
3
4
5
6
7
8
9
10
11
12
13
$ wg
interface: wg0
  public key: <redacted>
  private key: (hidden)
  listening port: 44195

peer: redacted=
  preshared key: (hidden)
  endpoint: <server's public IP address>:51820
  allowed ips: 10.213.213.0/24
  latest handshake: 49 seconds ago
  transfer: 50.94 MiB received, 971.40 KiB sent
  persistent keepalive: every 25 seconds

Additional configuration

  • [WireGuard] and [WireGuardPeer] sections in man 5 systemd.netdev (online versions: Debian, Arch, latest)
  • Quick Start on official website

Copyright © 2019, Elouan Martinet (Exagone313) — This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.