As you already know I host most of the services I use on a daily basis myself. Some of these services are hosted on the public Internet, while others are running on my home server. In this post I will cover the basic on how to get a peers-to-site VPN connection with WireGuard.

WireGuard uses a simple public/private keypair to set up an encrypted VPN tunnel, therefore there is no need for a complicated certificate authority setup. In my opinion this is a major step when upgrading from e.g. OpenVPN. Also, WireGuard is superfast and included in all newer versions of the Linux kernel.

In my case both the server and the client are running Arch Linux and the only additional package you have to install is wireguard-tools.

Set up a server

The server must be a machine with a public IPv4 and/or v6 address. In my case I use the cheapest Hetzner cloud machine CX11 which costs about 4 Euro a month.

At first generate keys with wg genkey | (umask 0077 && tee server.key) | wg pubkey > server.pub. Then use the following file as template, replace the needed variables and save it at /etc/wireguard/wg-server.conf. The preshared key can simply be generated with wg genpsk > peer1-peer2.psk. Each connection or peer needs its own preshared key!

    # wg-server.conf
    [Interface]
    Address = 10.0.0.1/24,fd42:42:42::1/64
    ListenPort = 1194
    PrivateKey = <generated-private-key>
    PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
    PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE

    # Home-VPN Gateway
    [Peer]
    PublicKey = <generated-client-public-key>
    AllowedIPs =  10.0.0.2/32, fd42:42:42::2/128, 192.168.0.0/24
    PresharedKey = <preshared-key>

    # Example for a roadwarrior client
    [Peer]
    PublicKey = <generated-public-key>
    AllowedIPs = 10.0.0.3/32,fd42:42:42::3/128
    PresharedKey = <preshared-key>

The important part is the network 192.168.0.0/24 in the AllowedIPs part of the gateways peer config. This will add a route on the server side, so the server knows that your home network can be reached through this specific WireGuard interface.

Enable IP forwarding with the following commands. -w will make these settings persistent across reboots.

    $ sysctl -w net.ipv4.ip_forward=1
    $ sysctl -w net.ipv6.conf.all.forwarding=1

Start the client with wg-quick up wg-server or systemctl enable --now wg@wg-server.

Configure the client

Again generate keys with wg genkey | (umask 0077 && tee client.key) | wg pubkey > client.pub. Then use the following file as template, replace the needed variables and save it at /etc/wireguard/wg-client.conf.

# wg-client.conf

[Interface]
PrivateKey = <generated-private-key>
Address = 10.0.0.2/24,fd42:42:42::3/64

[Peer]
PublicKey = <generated-server-public-key>
Endpoint = my.server.com:1194
AllowedIPs = 10.0.0.0/24,fd42:42:42::0/64,192.168.0.1/24
PresharedKey =  <preshared-key>
PersistentKeepalive = 25

PersistentKeepalive will ensure that the connection will be open, even if there is no need for it. As WireGuard is a silent protocol there will be no communication without a client sending or recieving messages. This is especially critical with peers behind NAT, which will likely be the case with almost every home setup.

Again enable IP forwarding with the following commands, as we want to forward traffic from local clients to VPN clients.

    $ sysctl -w net.ipv4.ip_forward=1
    $ sysctl -w net.ipv6.conf.all.forwarding=1

Start the client with wg-quick up wg-client or systemctl enable --now wg@wg-client.

To route all traffic through the tunnel you can use 0.0.0.0/0 in the AllowedIPs section.

Static route

The last thing to do is to set a static route to your VPN network. You can do this on each home device or simply add a static IPv4/v6 route on your Router. The route should look like this 10.0.0.0/24 via <ip of your client on the local network>.

At my home network the Pi I’m using as the client has the IP 192.168.0.8, therefore the static route looks like this 10.0.0.0/24 via 192.168.0.8.

Debugging

As WireGuard is a very silent protocol it can be hard the debug in case of any connectivity problems. Try to ping from each side of the tunnel and use wg show to check if the initial handshake was successfull. Check the logs with dmesg -wT or journalctl -u wg@wg-client/server.

    $ modprobe wireguard
    $ echo module wireguard +p > /sys/kernel/debug/dynamic_debug/control

Performance

I simply used iperf3 to run an upload and download test from my home to the server. As I have an 1000 Mbit download and 50 Mbit upload connection we should see some useful results.

Let’s start with the upload test. This is more or less my max. possible upload, therefore this a very good result.

➜  ~ iperf3 -c 10.66.66.1
Connecting to host 10.66.66.1, port 5201
[  5] local 192.168.0.28 port 34676 connected to 10.66.66.1 port 5201
[ ID] Interval           Transfer     Bitrate         Retr  Cwnd
[  5]   0.00-1.00   sec  6.20 MBytes  52.0 Mbits/sec    1    179 KBytes
[  5]   1.00-2.00   sec  5.76 MBytes  48.3 Mbits/sec    1    151 KBytes
[  5]   2.00-3.00   sec  5.70 MBytes  47.8 Mbits/sec    0    178 KBytes
[  5]   3.00-4.00   sec  5.82 MBytes  48.8 Mbits/sec    1    152 KBytes
[  5]   4.00-5.00   sec  6.00 MBytes  50.3 Mbits/sec    0    179 KBytes
[  5]   5.00-6.00   sec  6.00 MBytes  50.3 Mbits/sec    2    110 KBytes
[  5]   6.00-7.00   sec  5.46 MBytes  45.8 Mbits/sec    0    143 KBytes
[  5]   7.00-8.00   sec  6.00 MBytes  50.3 Mbits/sec    1    122 KBytes
[  5]   8.00-9.00   sec  6.00 MBytes  50.3 Mbits/sec    0    152 KBytes
[  5]   9.00-10.00  sec  5.76 MBytes  48.3 Mbits/sec    1    128 KBytes
- - - - - - - - - - - - - - - - - - - - - - - - -
[ ID] Interval           Transfer     Bitrate         Retr
[  5]   0.00-10.00  sec  58.7 MBytes  49.2 Mbits/sec    7             sender
[  5]   0.00-10.02  sec  58.1 MBytes  48.7 Mbits/sec                  receiver

iperf Done.

Now to the download test which is not as good as expected. This is about 1/4 of the possible download. The server has about 35-40 % CPU load while running the test, so it looks like the cheap VM might be the limit here.

➜  ~ iperf3 -c 192.168.0.28
Connecting to host 192.168.0.28, port 5201
[  5] local 10.66.66.1 port 52106 connected to 192.168.0.28 port 5201
[ ID] Interval           Transfer     Bitrate         Retr  Cwnd
[  5]   0.00-1.00   sec  26.2 MBytes   219 Mbits/sec    0   1.31 MBytes
[  5]   1.00-2.00   sec  31.2 MBytes   262 Mbits/sec  379   1.41 MBytes
[  5]   2.00-3.00   sec  31.2 MBytes   262 Mbits/sec    0   1.55 MBytes
[  5]   3.00-4.00   sec  31.2 MBytes   262 Mbits/sec  146   1.24 MBytes
[  5]   4.00-5.00   sec  26.2 MBytes   220 Mbits/sec    0   1.24 MBytes
[  5]   5.00-6.00   sec  26.2 MBytes   220 Mbits/sec    0   1.30 MBytes
[  5]   6.00-7.00   sec  26.2 MBytes   220 Mbits/sec    0   1.35 MBytes
[  5]   7.00-8.00   sec  27.5 MBytes   231 Mbits/sec    0   1.38 MBytes
[  5]   8.00-9.00   sec  27.5 MBytes   231 Mbits/sec    0   1.39 MBytes
[  5]   9.00-10.00  sec  33.8 MBytes   283 Mbits/sec    0   1.40 MBytes
- - - - - - - - - - - - - - - - - - - - - - - - -
[ ID] Interval           Transfer     Bitrate         Retr
[  5]   0.00-10.00  sec   287 MBytes   241 Mbits/sec  525             sender
[  5]   0.00-10.05  sec   286 MBytes   238 Mbits/sec                  receiver

iperf Done.

But this is more than enough for my day to day computing and even streaming a movie from my jellyfin instance at home.

Conclusion

I am a huge fan of WireGuard as it is simple to set up and fast. The tooling is easy to use (especially when compared against OpenVPN), the documentation is straight forward, and it simply works. And if you are running a recent Linux kernel most of the things you need are already there.