Wireguard ist eine moderne VPN-Lösung, die vor allem durch die einfache Konfiguration und hohe Performance immer beliebter wird. Behauptet wird zudem, dass Wireguard die sicherste VPN-Lösung sei, unter anderem dadurch, dass der Source-Code nur einen Bruchteil des Umfangs anderer Lösungen habe. Wireguard ist Cross-Plattform und damit auf quasi allen modernen Betriebsystemen wie Linux, Windows, Mac, Android und iOS verfügbar.

In diesem Post möchte ich auf die grundlegende Einrichtung von Wireguard auf einem Linux-Server eingehen und dabei die wesentlichen Konzepte und Parameter erklären.

Grundwissen

Wireguard arbeitet technisch mit sogenannten Peers. Es wird immer eine verschlüsselter Punkt-zu-Punkt (End-to-End) Tunnel zwischen zwei Peers aufgebaut. Anders als bei anderen VPN-Lösungen wird daher nicht zwischen Clients und Servern unterschieden. Deshalb ist die Konfiguration von Clients und Servern sehr ähnlich.

Jeder Peer hat dabei ein Schlüsselpaar bestehend aus Private- und Public-Key. Es wird daher eine asymetrische Verschlüsselung verwendet. Der Partner-Peer kennt dabei immer nur den Public-Key, den er zum Verschlüsseln der Daten nutzt. Der eigene Private-Key muss geheimgehalten werden und dient zum Entschlüsseln der Daten. Zusätzlich kann ein Passwort (Pre-shared Key) eingesetzt werden, das beiden Peers bekannt sein muss.

Soll nun ein Peer als “Wireguard VPN-Server” dienen, heißt das lediglich, dass dieser Peer mehrere Peering-Partner hat. Es werden also mehrere, voneinander unabhängige Tunnelverbindungen aufgebaut, die eine Stern-Struktur bilden. Der Server-Peer verwendet dabei dasselbe Schlüsselpaar, wobei alle Client-Peers denselben Public-Key kennen.

Ob über den Server-Peer nun Kommunikation zwischen den einzelnen Client-Peers stattfinden kann oder ob die Client-Peers über den Server-Peer im Internet surfen können ist eine Frage der Routing- und Firewallkonfiguration des Hostsystems und nicht von Wireguard. Es sollte daher ein gewisses Grundwissen über IP-Routing und Firewalls vorhanden sein.

Konfiguration des Server-Peers

Wireguard ist als Modul im Linuxkernel umgesetzt. Es ist also nötig einen aktuellen Kernel mit mindestens Version 5.6 zu verwenden. In jedem Fall müssen die Kommandozeilen-Tools wg und wg-quick installiert werden, über die der Tunnel konfiguriert werden kann. Bei Debian- und Ubuntu-Systemen heißt das Paket einfach wireguard-tools und kann mit sudo apt install wireguard-tools installiert werden.

Zur Konfiguration wird die Configdatei /etc/wireguard/wg0.conf angelegt. Der Name ist (bis auf die Dateiendung) frei wählbar, aber entspricht dem Namensschema des offiziellen Quickstart-Guides. Hier ein Beispiel inkl. Regeln für das Internetrouting (siehe Abschnitt Internetrouting).

# Konfiguration des SERVER-Peers (/etc/wireguard/wg0.conf)
[Interface]
Address    = 10.0.1.1/24
ListenPort = 54321
PrivateKey = kNSNZaYfpIFP0GOlliBnibWudKg1FroH7UB+Pp+s2HI=
PostUp     = iptables -t nat -A POSTROUTING -o enp0s3 -j MASQUERADE
PostDown   = iptables -t nat -D POSTROUTING -o enp0s3 -j MASQUERADE

# Client A
[Peer]
PublicKey  = 6FnnYgd8MgKz7kwfCVAT0EZZ3Zcz3FzctANcl1M52CE=
AllowedIPs = 10.0.1.20/32

# Client B
[Peer]
PublicKey  = e4NgtjOkjViYqhDdKXC3T7KQYJcUsVV15KGrlg3N20o=
AllowedIPs = 10.0.1.21/32

Bei [Interface] beginnt die Konfiguration des eigenen Tunnelendpunkts. Dazu gehören:

  • Address: Die eigene IP-Adresse im Wireguard-Tunnel. Mit dem /24 wird ein Adressbereich definiert, was für das Routing relevant sein wird.
  • ListenPort: Der UDP Netzwerk-Port, über die die Tunnelverbindung läuft. Kann bei Client-Peers weggelassen werden.
  • PrivateKey: Der eigene Private-Key. Der Public-Key kann daraus generiert werden und wird daher nicht eingestellt.
  • PostUp und PostDown: Befehle, die bei Start und Stopp ausgeführt werden. Das wird im folgenden Abschnitt genauer erklärt.

Anschließend wird mit jedem [Peer]-Abschnitt ein Partner-Peer konfiguriert. Die Konfiguration ist in der Regel sehr einfach und besteht hier nur aus zwei nötigen Parametern:

  • PublicKey: Der Public-Key des Partner-Peers, hier also eines Client-Peers.
  • AllowedIPs: Die Tunnel IP-Adresse(n), die mit diesem Peer verknüpft werden. Dies kann nur ein Client-Peer sein oder ein ganzes Netz.

Ein Schlüsselpaar kann mit dem folgenden Befehl generiert werden:

umask 077  # damit erzeugte Dateien nur für den Besitzer lesbar sind
wg genkey > privatekey.txt
wg pubkey < privatekey.txt > publickey.txt

Um Wireguard zu Starten kann das Tool wg-quick verwendet werden, das die Tunnel-Schnittstelle anlegt, die IP-Adresse setzt und PostUp und PostDown Befehle ausführt. Die dabei ausgeführten Befehle werden dabei angezeigt und es werden ggf. Fehler sichtbar. Beispielausgabe:

$ sudo wg-quick up wg0
[#] ip link add wg0 type wireguard
[#] wg setconf wg0 /dev/fd/63
[#] ip -4 address add 10.0.1.1/24 dev wg0
[#] ip link set mtu 1420 up dev wg0
[#] iptables iptables -t nat -I POSTROUTING -o enp0s3 -j MASQUERADE

Alternativ kann auch die systemd-Unit verwendet werden, z. B. sudo systemctl start wg-quick@wg0. Damit kann auch eine automatische Verbindung bei Systemstart einfach aktiviert werden: sudo systemctl enable wg-quick@wg0. Der Status kann über den kurzen Befehl wg angesehen werden:

$ sudo wg
interface: wg0
  public key: OpQ+Usx1ZyooZWa3tIOvH6CLJwFrS3isEC28NYAaJmc=
  private key: (hidden)
  listening port: 54321

peer: 6FnnYgd8MgKz7kwfCVAT0EZZ3Zcz3FzctANcl1M52CE=
  allowed ips: 10.0.1.20/32

peer: e4NgtjOkjViYqhDdKXC3T7KQYJcUsVV15KGrlg3N20o=
  allowed ips: 10.0.1.21/32

Konfiguration eines Client-Peers

Analog zu dem Server-Peer werden auch die Client-Peers eingerichtet. An dieser Stelle soll erneut ein Linux-System konfiguriert werden, um das Prinzip in Textform zeigen zu können. Auf den meisten Betriebssystemen gibt es auch grafische Oberflächen, aber die Konfiguration erfolgt mit den gleichen Parametern.

Der folgende Codeblock zeigt ein Beispiel passend zur zuvor gezeigten Konfiguration für den Server-Peer.

# Konfiguration des Peers "Client A" (/etc/wireguard/wg0.conf)
[Interface]
PrivateKey = YPc+21+sXiwyiV+nvdBMXrADydX39lFdumPKkDmipEM=
Address    = 10.0.1.20/24
DNS        = 8.8.8.8

# Server-Peer
[Peer]
PublicKey  = OpQ+Usx1ZyooZWa3tIOvH6CLJwFrS3isEC28NYAaJmc=
AllowedIPs = 0.0.0.0/0
Endpoint   = server.example.com:54321

Auch hier gibt es wieder einen [Interface]-Abschnitt für den eigenen Tunnelendpunkt. Anders als beim Server-Peer kann hier in der Regel alles bis auf den eigenen Private Key und die eigene Tunneladresse ausgelassen werden. Bei dem Address Parameter kann hier die Angabe des Netzes (/24) auch weggelassen werden. Zusätzlich sollte für die Internetnutzung ein DNS-Server über den Parameter DNS definiert werden.

Bei dem [Peer]-Abschnitt wird nun der einzige Peer des Clients konfiguriert, nämlich der Server-Peer. Als ein Parameter wird dazu der Public Key des Server-Peers benötigt. Der Parameter AllowedIPs gibt hier den gesamten IPv4-Adressbereich an (0.0.0.0/0), damit über den Server-Peer Pakete aus dem gesamten Internet empfangen werden dürfen. Außerdem nutzt wg-quick diese Information, um das Routing auf dem Client-Hostsystem passend zu konfigurieren.

Der letzte Parameter Endpoint ist nötig, um überhaupt einen Tunnel zum Server-Peer aufbauen zu können. Über diesen wird der Domain-Name oder die IP-Adresse und der Port des Servers angegeben. Der Port muss mit dem ListenPort in der Server-Peer Konfiguration übereinstimmen.

Die Verbindung kann wie beim Server aufgebaut werden (siehe oben). Mit dem Befehl wg kann geprüft werden, ob eine Verbindung aufgebaut ist. In der Ausgabe sieht man, wie viele Daten bereits durch den Tunnel geflossen sind:

$ sudo wg
interface: wg0
  public key: 6FnnYgd8MgKz7kwfCVAT0EZZ3Zcz3FzctANcl1M52CE=
  private key: (hidden)
  listening port: 50072
  fwmark: 0xca6c

peer: OpQ+Usx1ZyooZWa3tIOvH6CLJwFrS3isEC28NYAaJmc=
  endpoint: [2001:db8::4534:ab5f:33bf:7d3c]:54321
  allowed ips: 0.0.0.0/0
  latest handshake: 45 seconds ago
  transfer: 4.67 KiB received, 501.36 KiB sent

Auf der Serverseite können außerdem bei endpoint die aktuellen öffentlichen IP-Adresse der verbundenen Client-Peers gesehen werden.

Internetrouting

Bislang können nur Server und Client miteinander kommunizieren. Um nun den gesamten Internetverkehr durch den Wireguard-Tunnel leiten zu können, muss noch das Routing eingerichtet werden.

Als erstes muss das Weiterleiten von Paketen im Kernel aktiviert werden, was standardmäßig nicht der Fall ist. Dazu sollte z. B. in der Datei /etc/sysctl.d/90-ipforwarding.conf die folgende Zeile gespeichert werden:

net.ipv4.ip_forward=1

Danach zum Übernehmen entweder den Befehl sudo sysctl --system ausführen oder den Server neustarten.

Weil der Server und die Clients sich in der Regel die IPv4-Adresse des Servers teilen müssen, muss NAT verwendet werden. Dazu sind in der wg0.conf die PostUp und PostDown Parameter zuständig. Anzupassen ist dort der Name der Netzwerkschnittstelle, über den der Server auf das Internet zugreift, anzupassen. In der gezeigten Configdatei ist das bspw. enp0s3.

Nicht zuletzt muss die Firewall eingerichtet werden, falls eine verwendet wird. Ich gehe an dieser Stelle davon aus, dass ufw verwendet wird. Die folgenden Regeln müssen hinzugefügt werden:

# Tunnelendpunkt erreichbar machen
sudo ufw allow 54321/udp
# Daten aus dem Tunnel annehmen
sudo ufw allow in on wg0
# Weiterleiten von Internetverkehr
sudo ufw route allow in on wg0
sudo ufw route allow out on wg0

Tipps zur Fehleranalyse

Mit wg und Tools wie ping kann einfach getestet werden, ob die Verbindung funktioniert. Sollte keine Internetverbindung möglich sein, sollte zunächst geprüft werden, ob der Server-Peer überhaupt erreichbar ist. Ist dies nicht der Fall, sollten als erstes die Konfigurationsdateien genau geprüft werden.

In dem folgenden Beispiel ist zwar der Server-Peer erreichbar, aber keine Internetverbindung möglich:

client$ ping 10.0.1.1
PING 10.0.1.1 (10.0.1.1) 56(84) bytes of data.
64 bytes from 10.0.1.1: icmp_seq=1 ttl=64 time=8.42 ms
^C
client$ ping 8.8.8.8
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
From 10.0.1.1 icmp_seq=1 Destination Host Prohibited

Das lässt auf ein Routing- oder Firewallproblem schließen. Routingregeln lassen sich mit ip route get <IP-Adresse> prüfen. Hier sollte für den Client-Peer das Wireguard-Interface herauskommen, z. B. dev wg0:

<IP-Adresse> dev wg0 table 51820 src 10.0.1.20 uid 0

Auch könnte die Firewall kurz zu deaktiviert und erneut probiert werden.

Für Systeme ohne ufw, zum Beispiel in der Oracle Cloud, müssen die äquivalenten iptables Regeln in PostUp und PostDown mit untergebracht werden:

PostUp   = iptables -I INPUT 1 -i %i -j ACCEPT; iptables -I FORWARD 1 -i %i -j ACCEPT; iptables -I FORWARD 1 -o %i -j ACCEPT; iptables -t nat -I POSTROUTING 1 -o enp0s3 -j MASQUERADE
PostDown = iptables -D INPUT -i %i -j ACCEPT; iptables -D FORWARD -i %i -j ACCEPT; iptables -D FORWARD -o %i -j ACCEPT; iptables -t nat -D POSTROUTING -o enp0s3 -j MASQUERADE

Wichtig ist die Angabe einer Regelposition (-I <chain> 1), da bei den Images der Oracle Cloud die letzte Regel per default das Zurückweisen des Paketes ist (reject-with icmp-host-prohibited). Alternativ können die Regeln auch in /etc/iptables/rules.v4 eingetragen werden.

Performance-Fix durch Anpassung der MTU

Ich hatte beim Testen feststellen müssen, dass die Wireguard-Tunnel Performance nicht so hoch war wie erwartet. Bei Speedtests kamen so gerade einmal rund 50 Mbit/s über meine 250 Mbit/s DSL-Leitung durch. Schuld daran war die maximale Paketgröße (MTU), die standardmäßig etwas zu hoch eingestellt war. Dadurch werden die Pakete unnötig oft auf der Strecke aufgeteilt und wieder zusammengepuzzelt, was einen sichtbar großen Overhead erzeugt.

Mit dem Tool iperf3 lässt sich der Durchsatz testen. Auf dem Server wird es einfach mit iperf3 -s ausgeführt. Auf dem Client mit iperf3 -c <Server-IP>. Gemessen wird damit der Durchsatz vom Client zum Server (upstream). Die Richtung lässt sich tauschen, indem zusätzlich der Client-Parameter -R verwendet wird oder der iperf3-Server auf dem VPN-Client gestartet wird.

Mit einer MTU von 1360 Bytes konnte ich fast die volle Bandbreite nutzen. Bei Problemen mit der Performance kann also mit der MTU etwas experimentiert werden. Das Ziel sollte sein die MTU so hoch wie möglich zu wählen, um den Overhead gering zu halten. Durch die gravierenden Performanceunterschiede sollte die Grenze schnell gefunden werden können. Wenn der Durchsatz in Richtung Gbit/s geht, sollte außerdem die CPU-Last beobachtet werden, nicht dass hier der Flaschenhals sitzt.