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.