Wireguard + systemd-network easy setup (VPN/ptp tunnel)

I’ve been asked before to post something about this and I know many people are interested in VPN solutions that make sense for the cloud - these are very different than consumer VPN solutions!

So, this wireguard topic is for people who, like me, deploy could services in various locations that want their cloud services to talk to other cloud services over an encrypted tunnel. That makes them point-to-point tunnels, inherently. And so this topic doesn’t cover much more than static ptp routing and ignores lots of other things.

While this may seem a little bland and limited, in fact it isn’t and offers lots of functionality. One of the use cases I’ve been struggling with for a long time is limited storage in cloud services. I don’t want
to revert to sshfs on cloud services since it offers no reliability and is a pain to maintain - I need a much more reliable method to host files between two different cloud nodes. What I actually want is cifs, but, I’d want all the data to be … encrypted.

One of the second use cases is being able to reach devices which are on a random IP address. You can certainly setup dynamic DNS, but, really, It’s a pain to have to maintain a DNS server just for that. Of course, you can use a cloud service for it too, but, what if your device is on a mobile phone connection and is basically firewalled?

Wireguard takes care of a lot of these problems all at once - the tunnel between the two endpoints is persistent from a userspace perspective - the link will always exist - you can set it to use static IP addresses for clients and now you can just simply do things like mount a cifs file system or ssh to one of the static private IP addresses when you need to, or do things like mqtt and have clients update statuses and retrieve messages when they’re connected - ideal for IOT scenarios.

setup basics

To start, we need to have 2 devices. We’re going to mark one as a server and one as a client. The server will be listening on a fixed udp port for consistency. We can add more clients that connect to the same server that way.

On each device, we’re going to make 2 files in /etc/systemd/network. These define the parameters for (1) systemd-networkd to create the wireguard kernel device, and (2) how to route packets over it.

We are going to need public and private keys. Each wireguard endpoint will need a public key to decrypt data, and a private key to encrypt data. Because we need data to flow both ways, we therefore need 2 public keys and 2 private keys. We could absolutely use the same keys for data flowing both ways, but, keys are cheap and more keys create more complexity, so it’s good practice to make additional keys for each client.

You’ll have to do the following step therefore twice:

# generate private key 1 $ wg genkey
XOSnurQik@Yqyo$V6vEuzd%hvZwdMi0xRyfYqjPtzhw=

# generate pubkey 1 $ echo
XOSnurQik@Yqyo$V6vEuzd%hvZwdMi0xRyfYqjPtzhw= | wg pubkey
ybiHD3h!rW5Zps476*iH/CJgORrGJra^ilud6NDpxEc=

(Don’t worry, these are fake keys)

Now we have 2 keys. The first one is the private key and the second one is the public key. We’re going to use the private key on one side of the tunnel, and the public key on the other
side of the tunnel.

Do this again to make private key 2 and public key 2.

Now that we have 2 key pairs, we’re going to embed them in the systemd-networkd configuration files. First, here’s the server configuration file /etc/systemd/network/wg0.netdev:

[NetDev]
Name=wg0
Kind=wireguard
Description=Wireguard link

[WireGuard]
PrivateKey=<private_key_1>
ListenPort=49253

[WireGuardPeer]
PublicKey=<public_key_2>
AllowedIPs=0.0.0.0/0
AllowedIPs=::/0
PersistentKeepalive = 25

Note, this is the server, and it allows any IP address to connect from anywhere. Not that that is unsafe, because wireguard will not respond to any packet that has been encrypted with the correct public key, and that one will live only on that client. The port number can be anything, of course, I picked a random value here for this example.

Make sure to substitute the <private_key_1> and <public_key_2> text with the actual content of the keys you generated earlier (ending with the = character). No need for " characters around it.

This netdev file contains enough information for systemd-networkd to initialize wiregaurd in the kernel, setup the keys and make wireguard listen for incoming packets from a client that has the proper keys.

To properly route packages through the tunnel, the server also needs routing and IP information, and that it gets from the /etc/systemd/network/wg0.network file:

[Match]
Name = wg0

[Network]
Address = 10.0.0.1/32

[Route]
Destination = 10.0.0.2

To put it simple: This just configures the server to connect each (1) peer to be at 10.0.0.2 while the server itself is at 10.0.0.1. You should really pick a private IP range for a private tunnel like this, but you can obviously ignore that if you know what you’re doing.

On the client side, things are about as complex.

/etc/systemd/network/wg0.netdev contains:

[NetDev]
Name=wg0
Kind=wireguard
Description=Wireguard link

[WireGuard]
PrivateKey=<private_key_2>
# on the client, listenport is irrelevant
ListenPort=54918

[WireGuardPeer]
PublicKey=<public_key_1>
AllowedIPs=0.0.0.0/0
AllowedIPs=::/0
Endpoint=<server_static_ip:port>
PersistentKeepalive = 25

Note here again the 2 keys - they are the inverse from the server netdev file. The most important entry in this file is the Endpoint entry which should list your server IP address and server listening port. If those do not match, your client will never be able to connect to the server. The ListenPort on the client isn’t used, since we have no client connect to this client. Same goes for the AllowedIPs.

The network file is similarly trivial as on the server:

/etc/systemd/network/wg0.network:

[Match]
Name = wg0

[Network]
Address = 10.0.0.2/32

[Route]
Destination = 10.0.0.1

And that’s all there is to it.

Starting it up

The only thing needed now is to systemctl restart systemd-networkd (Edit: On both machines, of course!). Nothing else is needed, everything will be taken care of. You can inspect the tunnel with sudo wg on both ends to verify keys and ports are setup and see client status.

You can certainly fancy things up from these “starter” wireguard files and add things like DNS, default routing and more. Having just a simple point to point tunnel to start can really help to get your VPN setup quickly and avoid dealing with the complexities of other VPN sulutions.

One really attractive property of wireguard devices is that they don’t depend on any running userspace software. You can restart systemd-networkd or even just shut it down, and the tunnel will remain operational. If the endpoint goes offline for a bit, everything will reconnect without user interaction. I’ve wrestled in the past with SSH and openvpn and nothing really seems as simple and clean as wg in combination with systemd-networkd here. Yes I know wg-quick works too, but that doesn’t do anything at boot unless you make units for it, which are just as long as these 4 files, and hey, we’re already likely running systemd-networkd on the systems, especially in a cloud setting.

Feel free to post additional suggestions and ideas! Hope you enjoyed it.

PS: Clearlinux carries the wireguard kernel module by default. This may not be the case in other OSs.

1 Like

I’ve seen other tutorials also enabled ip forwarding. Is that a good practice? And is it necessary with Wireguard?

Excellent point! It is not required at all to enable ip_forward with sysctl to make point-to-point tunnels work! You can certainly enable it, and if you do end up configuring a default route through the tunnel, you will need to enable it for the routing to work.

In my example above, I specifically explain that I am not looking for something like a privacy VPN where you browse the internet from a client and route all packets through it. That is more complex, but, I suspect only slightly so - adding some routing and enabling ip forwarding can all be done in the network files.

1 Like

Which cloud service with Clear Linux support would you recommend?

Any that you can install with clearlinux easily, or even ones that come with clearlinux preinstalled :wink:

Hetzner, Netcup, Upcloud, Vultr

1 Like