Years ago I set up OpenVPN for a customer. It works but is a bit cumbersome to get running. WireGuard is a new alternative, that is very easy to set up and maintain and also is very well designed from the technical point of view. It is also very performant.

A nice kind-of-vpn alternative is sshuttle.

Setting it up

  1. Install WireGuard: https://www.wireguard.com/install/
  2. And configure it…

There is actually no difference between client and server, except that the server listens on a fixed port (UPD), so there is a well defined point for the client to connect to.

Server

On the server create /etc/wireguard/wg0.conf with 600 permissions.

[Interface]
Address = 192.168.10.1/24, fd10::1/64
ListenPort = 51820
PrivateKey = <server_private_key>

[Peer]
PublicKey = <client_public_key>
AllowedIPs = 192.168.10.2, fd10::2

The [Interface] block makes the server listen on port 51820 (IPv4 and 6) and assigns the interface wg0 the given IP adresses (4 and 6 in this case). Use wg genkey to generate a private key. (Setup UPD port forwarding in your router if the machine running the WG server is behind a NAT.)

The [Peer] block allows incoming packages from a peer with the given public key and IP adresses (VPN internal). The public key can be derived from the private key with wg pubkey.

You find more details in the man page of wg.

Client

On the client create /etc/wireguard/wg0.conf with 600 permissions, too.

[Interface]
Address = 192.168.10.2/24, fd10::2/64
PrivateKey = <client_private_key>

[Peer]
PublicKey = <server_public_key>
Endpoint = <server_fqdn_or_ip>:51820
AllowedIPs = 192.168.10.0/24, fd10::/64

This is the matching opposite to the server configuration with different IP addresses for the client. The Endpoint tells WG where it can reach the server. The AllowedIPs also causes routes to be set up, which cause the traffic for the specified address ranges to be routed through the VPN.

Now start the VPN with sudo wg-quick up wg0 on client and server. That’s it. sudo wg show shows the current status. Due to IP roaming and cryptokey routing, there is no need to reconnect, when the client’s IP addresses changes or the connection is lost. Stop the VPN with sudo wg-quick down wg0 (on the client, leave it up on the server).

You can add more clients, each with it’s own IP addresses and keys. Create a config and private key for each client and add the client’s pubkey key to the server config in another [Peer] section.

Change the addresses ranges to whatever you like.

It is possible to have systemd keeping the VPN up, enable it with sudo systemctl enable wg-quick@wg0.

Routing

The example above allows a connection between client and server only (dashed line). Usually you want the client to be able to access the entire local network of server (the Ms). To make this possible, the configuration hast to be extended (the ugly part).

graph LR
C[client] -.-> I((internet))
I -.-> R(router/NAT)
subgraph local network
R -.-> S[server]
R --- M1
R --- M2
R --- M3
end

Let’s assume the server is connected to the local network on eth0 with 10.0.0.10/24 and fd00::10/64. Then we change the AllowedIPs in the client config:

AllowedIPs = 192.168.10.0/24, fd10::/64, 10.0.0.0/24, fd00::/64

This does the following

  1. It allows traffic from the local network to reach the client through the VPN and
  2. sets up a route on the client, which directs traffic to these IP ranges through the VPN.

On the server we need to configure forwarding and masquerading, to the [Interface] section we add:

PostUp   = iptables -A FORWARD -i %i -j ACCEPT; iptables -A FORWARD -o %i -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE; ip6tables -A FORWARD -i %i -j ACCEPT; ip6tables -A FORWARD -o %i -j ACCEPT; ip6tables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -D FORWARD -o %i -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE; ip6tables -D FORWARD -i %i -j ACCEPT; ip6tables -D FORWARD -o %i -j ACCEPT; ip6tables -t nat -D POSTROUTING -o eth0 -j MASQUERADE

Replace eth0 with the actual name of the interface.

It is also possible to route the entire traffic (internet access) through the VPN by setting

AllowedIPs = 0.0.0.0/0, ::/0

on the client. And adding

DNS = 10.0.0.1, fd00::1

to the [Interface] section of the client (assuming the router is the DNS server for the local network with IP addresses 10.0.0.1 and fd00::1) sets the client’s primary DNS. This way DNS is performed through the VPN and even the names of machines in the local network are resolved.

It is possible to do this without IP masquerading as well, but this requires static routes to the VPN with the server as gateway to be configured on the router of the local network.