Eintrag

wg-easy v15: Migration und neues Setup

wg-easy v15: Migration und neues Setup

Mit Version 15 hat wg-easy einen kompletten Rewrite bekommen. Die Konfiguration wandert von Environment-Variablen in ein Admin Panel mit SQLite-Datenbank, es gibt einen Setup-Wizard, und die docker-compose.yml schrumpft auf ein Minimum zusammen. Klingt gut - aber was bedeutet das für bestehende Installationen?

Falls du WireGuard und wg-easy noch nicht kennst: Im WireGuard-Grundlagen-Post findest du alles zu Konzepten, Client-Einrichtung und Netzwerk-Konfiguration.


Was ist neu in v15?

Die wichtigste Änderung: Fast alles, was du bisher in der docker-compose.yml konfiguriert hast, passiert jetzt über das Web-Interface.

Aspekt v14 v15
Konfiguration Environment-Variablen in docker-compose.yml Admin Panel im Web-UI
Passwort PASSWORD_HASH (bcrypt) Username + Passwort via Setup Wizard
DNS WG_DEFAULT_DNS Env-Var Admin Panel > WireGuard > DNS Servers
Allowed IPs WG_ALLOWED_IPS Env-Var Admin Panel > WireGuard > Allowed IPs
PostUp/PostDown WG_POST_UP, WG_POST_DOWN Env-Vars Admin Panel > Hooks
Persistenz JSON-Dateien im Volume SQLite-Datenbank
Ersteinrichtung Container starten, fertig Setup Wizard im Browser
Env-Vars ~12 Variablen 3 Variablen (Port, Host, Path)

Die bestehenden Clients funktionieren nach der Migration weiter - du musst auf den Endgeräten nichts ändern, solange du die wg0.json importierst.


Frische Installation

Wenn du noch kein wg-easy hast, ist v15 der perfekte Einstieg. Die docker-compose.yml ist erfrischend kurz:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
services:
  wg-easy:
    image: ghcr.io/wg-easy/wg-easy:15
    container_name: wg-easy
    hostname: wg-easy
    environment:
      - PORT=51821
      - WG_HOST=vpn.example.com
      - WG_PATH=/
    volumes:
      - ./data:/etc/wireguard
    ports:
      - "51820:51820/udp"
      - "51821:51821/tcp"
    cap_add:
      - NET_ADMIN
      - SYS_MODULE
    sysctls:
      - net.ipv4.conf.all.src_valid_mark=1
      - net.ipv4.ip_forward=1
    restart: unless-stopped

Das war’s. Drei Environment-Variablen:

  • PORT - Der Port für das Web-Interface (Standard: 51821)
  • WG_HOST - Deine öffentliche Domain oder IP
  • WG_PATH - Der Pfad für das Web-UI (nützlich bei Reverse Proxies)
1
docker compose up -d

Beim ersten Aufruf von http://dein-server:51821 begrüßt dich der Setup Wizard. Dort legst du einen Admin-Account mit Username und Passwort an - kein bcrypt-Hash mehr, kein Generieren in der Shell.

WG_HOST muss die Adresse sein, unter der dein WireGuard-Server von außen erreichbar ist. Bei einer Cloud-VM ist das die öffentliche IP, hinter einem Router die DynDNS-Domain.


Migration von v14 auf v15

Die Migration ist nicht kompliziert, erfordert aber ein paar Schritte in der richtigen Reihenfolge.

Schritt 1: Backup erstellen

Bevor du irgendetwas änderst - sichere deinen aktuellen Stand:

1
2
3
4
5
# docker-compose.yml sichern
cp docker-compose.yml docker-compose.yml.v14.bak

# WireGuard-Konfiguration sichern
cp -r ./config ./config.v14.bak

Zusätzlich die Client-Konfigurationen als wg0.json exportieren. In wg-easy v14 öffnest du das Web-UI und lädst die Konfiguration herunter, oder du kopierst die JSON-Datei direkt aus dem Volume:

1
cp ./config/wg0.json ./wg0.json.bak

Ohne die wg0.json verlierst du alle Client-Konfigurationen. Diesen Schritt auf keinen Fall überspringen!

Schritt 2: Container stoppen

1
docker compose down

Schritt 3: docker-compose.yml umschreiben

Ersetze deine v14-Konfiguration mit der neuen v15-Version.

Vorher (v14):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
services:
  wg-easy:
    image: ghcr.io/wg-easy/wg-easy:14
    container_name: wg-easy
    environment:
      - WG_HOST=vpn.example.com
      - PASSWORD_HASH=$$2a$$12$$abc123...
      - WG_PORT=51820
      - WG_DEFAULT_ADDRESS=10.8.0.x
      - WG_DEFAULT_DNS=1.1.1.1, 9.9.9.9
      - WG_ALLOWED_IPS=0.0.0.0/0
      - WG_PERSISTENT_KEEPALIVE=25
      - WG_POST_UP=iptables -t nat -A POSTROUTING ...
      - WG_POST_DOWN=iptables -t nat -D POSTROUTING ...
      - LANG=de
    volumes:
      - ./config:/etc/wireguard
    ports:
      - "51820:51820/udp"
      - "51821:51821/tcp"
    cap_add:
      - NET_ADMIN
      - SYS_MODULE
    sysctls:
      - net.ipv4.conf.all.src_valid_mark=1
      - net.ipv4.ip_forward=1
    restart: unless-stopped

Nachher (v15):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
services:
  wg-easy:
    image: ghcr.io/wg-easy/wg-easy:15
    container_name: wg-easy
    hostname: wg-easy
    environment:
      - PORT=51821
      - WG_HOST=vpn.example.com
      - WG_PATH=/
    volumes:
      - ./data:/etc/wireguard
    ports:
      - "51820:51820/udp"
      - "51821:51821/tcp"
    cap_add:
      - NET_ADMIN
      - SYS_MODULE
    sysctls:
      - net.ipv4.conf.all.src_valid_mark=1
      - net.ipv4.ip_forward=1
    restart: unless-stopped

Beachte den neuen Volume-Pfad: ./data statt ./config. Wenn du den gleichen Pfad behalten willst, passe ihn entsprechend an. v15 erstellt eine neue SQLite-Datenbank, die alten JSON-Dateien werden nicht automatisch übernommen.

Schritt 4: Container starten

1
docker compose up -d

Schritt 5: Setup Wizard durchlaufen

Öffne http://dein-server:51821 im Browser. Der Setup Wizard erscheint und fragt nach:

  1. Admin-Account - Username und Passwort festlegen
  2. Client-Import - Hier die wg0.json hochladen

Nach dem Import sollten alle deine bestehenden Clients wieder sichtbar sein.

Schritt 6: Admin Panel konfigurieren

Nach dem Import musst du die Einstellungen prüfen und nachjustieren. v15 übernimmt nicht alle Konfigurationen aus der wg0.json:

  • DNS Servers - Im Admin Panel unter WireGuard > DNS Servers eintragen (z.B. 1.1.1.1, 9.9.9.9)
  • Allowed IPs - Unter WireGuard > Allowed IPs setzen (z.B. 0.0.0.0/0 für Full Tunnel)
  • Persistent Keepalive - Unter WireGuard > Persistent Keepalive
  • Hooks - PostUp/PostDown Regeln unter Admin > Hooks eintragen (siehe Abschnitt unten)

Nach dem Import sind die Hooks (PostUp/PostDown) leer und DNS steht auf dem Default. Beides manuell im Admin Panel nachtragen!


Stolperfalle: IPv6 auf Cloud-VMs

Wenn dein wg-easy auf einer Cloud-VM (Hetzner, Netcup, etc.) läuft und du beim Start folgende Fehlermeldung siehst:

1
2
ip6tables v1.8.10: can't initialize ip6tables table `filter':
Table does not exist (do you need to insmod?)

Dann fehlen die IPv6-Kernel-Module. Das ist bei vielen Cloud-Providern standardmäßig der Fall.

Lösung:

1
2
3
4
# Module laden
sudo modprobe ip6_tables
sudo modprobe ip6table_filter
sudo modprobe ip6table_nat

Persistent machen (überlebt Reboots):

1
echo -e "ip6_tables\nip6table_filter\nip6table_nat" | sudo tee /etc/modules-load.d/ip6tables.conf

Danach den Container neu starten:

1
docker compose restart wg-easy

Dieses Problem tritt nur auf Cloud-VMs auf, die mit einem minimalen Kernel ausgeliefert werden. Auf einer Proxmox-VM oder einem dedizierten Server hast du die Module in der Regel bereits geladen.


Hooks konfigurieren

In v14 hast du PostUp/PostDown als Environment-Variablen gesetzt. In v15 passiert das im Admin Panel unter Hooks.

Brauche ich MASQUERADE?

Kurze Antwort: Ja, in den meisten Fällen.

MASQUERADE (NAT) schreibt die Quell-IP der VPN-Clients auf die IP der wg-easy-VM um. Ohne MASQUERADE behalten die Pakete ihre WireGuard-IP (z.B. 10.10.10.3) - aber die Zieldienste müssen dann wissen, wie sie Antworten an dieses Subnetz zurückschicken. Dafür braucht jede Ziel-VM (oder das Gateway) eine statische Route für 10.10.10.0/24 über die wg-easy-VM.

Besonders wenn deine wg-easy-VM in einem anderen Subnetz als die Dienste sitzt (z.B. VPN in 192.168.45.0/24, Dienste in 192.168.60.0/24), funktioniert es ohne MASQUERADE und ohne statische Routen schlicht nicht - auch DNS-Anfragen bleiben unbeantwortet, und Clients können keine Hostnamen mehr auflösen.

Ohne MASQUERADE siehst du in den Logs der Zieldienste die einzelnen Client-IPs statt der VM-IP. Falls du diese Transparenz brauchst, richte statische Routen für 10.10.10.0/24 auf deinem Gateway oder den Ziel-VMs ein und lass MASQUERADE weg.

Split Tunnel (nur bestimmte Subnetze)

Bei einem Split Tunnel schickst du nur bestimmte Netze über den VPN:

Post Up:

1
iptables -t nat -A POSTROUTING -s {{ipv4Cidr}} -o {{device}} -j MASQUERADE; iptables -A INPUT -p udp -m udp --dport {{port}} -j ACCEPT; iptables -A FORWARD -i wg0 -j ACCEPT; iptables -A FORWARD -o wg0 -j ACCEPT

Post Down:

1
iptables -t nat -D POSTROUTING -s {{ipv4Cidr}} -o {{device}} -j MASQUERADE; iptables -D INPUT -p udp -m udp --dport {{port}} -j ACCEPT; iptables -D FORWARD -i wg0 -j ACCEPT; iptables -D FORWARD -o wg0 -j ACCEPT

Full Tunnel

Für einen Full Tunnel, bei dem der gesamte Traffic über den VPN läuft:

Post Up:

1
iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE; iptables -A FORWARD -i wg0 -j ACCEPT; iptables -A FORWARD -o wg0 -j ACCEPT

Post Down:

1
iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE; iptables -D FORWARD -i wg0 -j ACCEPT; iptables -D FORWARD -o wg0 -j ACCEPT

Whitelist-Firewall: Clients gezielt einschränken

In einem Homelab willst du externen Nutzern (Familie, Freunde) oft nur bestimmte Dienste freigeben, nicht das gesamte Netzwerk. Mit einer Whitelist-Firewall in den globalen Hooks erreichst du genau das:

Post Up:

1
iptables -t nat -A POSTROUTING -s {{ipv4Cidr}} -o {{device}} -j MASQUERADE; iptables -A INPUT -p udp -m udp --dport {{port}} -j ACCEPT; iptables -A FORWARD -i wg0 -j ACCEPT; iptables -A FORWARD -o wg0 -j ACCEPT; iptables-nft -I FORWARD 1 -o wg0 -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT; iptables-nft -I FORWARD 2 -i wg0 -d 192.168.60.65 -p tcp --dport 443 -j ACCEPT; iptables-nft -I FORWARD 3 -i wg0 -d 192.168.60.141 -p tcp --dport 32400 -j ACCEPT; iptables-nft -I FORWARD 4 -i wg0 -d 192.168.60.141 -p tcp --dport 8096 -j ACCEPT; iptables-nft -I FORWARD 5 -i wg0 -d 192.168.60.141 -p tcp --dport 5055 -j ACCEPT; iptables-nft -I FORWARD 6 -i wg0 -d 192.168.60.101 -p udp --dport 53 -j ACCEPT; iptables-nft -I FORWARD 7 -i wg0 -d 192.168.60.101 -p tcp --dport 53 -j ACCEPT; iptables-nft -I FORWARD 8 -i wg0 -d 192.168.60.102 -p udp --dport 53 -j ACCEPT; iptables-nft -I FORWARD 9 -i wg0 -d 192.168.60.102 -p tcp --dport 53 -j ACCEPT; iptables-nft -I FORWARD 10 -i wg0 -j DROP

Post Down:

1
iptables -t nat -D POSTROUTING -s {{ipv4Cidr}} -o {{device}} -j MASQUERADE; iptables -D INPUT -p udp -m udp --dport {{port}} -j ACCEPT; iptables -D FORWARD -i wg0 -j ACCEPT; iptables -D FORWARD -o wg0 -j ACCEPT; iptables-nft -D FORWARD -o wg0 -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT; iptables-nft -D FORWARD -i wg0 -d 192.168.60.65 -p tcp --dport 443 -j ACCEPT; iptables-nft -D FORWARD -i wg0 -d 192.168.60.141 -p tcp --dport 32400 -j ACCEPT; iptables-nft -D FORWARD -i wg0 -d 192.168.60.141 -p tcp --dport 8096 -j ACCEPT; iptables-nft -D FORWARD -i wg0 -d 192.168.60.141 -p tcp --dport 5055 -j ACCEPT; iptables-nft -D FORWARD -i wg0 -d 192.168.60.101 -p udp --dport 53 -j ACCEPT; iptables-nft -D FORWARD -i wg0 -d 192.168.60.101 -p tcp --dport 53 -j ACCEPT; iptables-nft -D FORWARD -i wg0 -d 192.168.60.102 -p udp --dport 53 -j ACCEPT; iptables-nft -D FORWARD -i wg0 -d 192.168.60.102 -p tcp --dport 53 -j ACCEPT; iptables-nft -D FORWARD -i wg0 -j DROP

Die Logik dahinter:

Regel Zweck
ESTABLISHED,RELATED Antwort-Traffic durchlassen
.65:443 Nginx Proxy Manager (HTTPS)
.141:32400 Plex
.141:8096 Jellyfin
.141:5055 Overseerr
.101:53, .102:53 DNS-Server (UDP + TCP)
DROP Alles andere blockieren

Die Template-Variablen {{ipv4Cidr}}, {{device}} und {{port}} werden von wg-easy v15 automatisch ersetzt. Du trägst sie genau so in die Hooks ein.

Client-spezifische Hooks: Admin-Zugang ohne Einschränkungen

Die Whitelist-Firewall oben schränkt alle Clients ein - auch einen Admin-Account, den du für die Verwaltung des Homelabs nutzt. Da v15 Hooks pro Client erlaubt, kannst du für den Admin-Client eigene Hooks setzen, die den globalen DROP umgehen.

Gehe dazu im Admin Panel auf den jeweiligen Client und trage dort folgende Hooks ein:

Post Up:

1
iptables -t nat -A POSTROUTING -s {{ipv4Cidr}} -o {{device}} -j MASQUERADE; iptables -A INPUT -p udp -m udp --dport {{port}} -j ACCEPT; iptables -A FORWARD -i wg0 -j ACCEPT; iptables -A FORWARD -o wg0 -j ACCEPT

Post Down:

1
iptables -t nat -D POSTROUTING -s {{ipv4Cidr}} -o {{device}} -j MASQUERADE; iptables -D INPUT -p udp -m udp --dport {{port}} -j ACCEPT; iptables -D FORWARD -i wg0 -j ACCEPT; iptables -D FORWARD -o wg0 -j ACCEPT

Das sind MASQUERADE und die Basis-Regeln für Forwarding - ohne die iptables-nft Whitelist und den abschließenden DROP. Damit hat der Admin-Client vollen Zugriff auf alle IPs und Ports im Homelab.

Falls die globalen Hooks zusätzlich zu den Client-Hooks ausgeführt werden, greift der globale DROP trotzdem. In dem Fall musst du im Admin-Client eine zusätzliche ACCEPT-Regel mit niedrigerer Nummer als der DROP einfügen: iptables-nft -I FORWARD 1 -i wg0 -s <admin-client-ip> -j ACCEPT

Nach dem Einrichten solltest du testen, ob der Admin-Client tatsächlich überall hinkommt - z.B. per ping oder ssh auf eine IP, die in der Whitelist nicht enthalten ist (etwa ein Proxmox-Node).


Rollback auf v14

Falls etwas schiefgeht, kannst du jederzeit auf v14 zurück:

1
2
3
4
5
6
7
8
9
10
# v15 stoppen
docker compose down

# Backup wiederherstellen
cp docker-compose.yml.v14.bak docker-compose.yml
rm -rf ./data
cp -r ./config.v14.bak ./config

# v14 starten
docker compose up -d

Deine Clients verbinden sich wieder wie gewohnt - an der WireGuard-Konfiguration auf den Endgeräten ändert sich beim Rollback nichts.


Troubleshooting

Container startet nicht: ip6tables-Fehler

Siehe Stolperfalle: IPv6 auf Cloud-VMs.

Clients nach Import nicht erreichbar

Prüfe im Admin Panel:

  1. Sind die Hooks (PostUp/PostDown) korrekt eingetragen?
  2. Stimmt das Netzwerk-Interface in den iptables-Regeln?
  3. Ist IP Forwarding auf dem Host aktiv (sysctl net.ipv4.ip_forward)?

DNS funktioniert nicht nach Migration

v15 setzt DNS auf den Default zurück. Im Admin Panel unter WireGuard > DNS Servers deine gewünschten DNS-Server eintragen.

Änderungen an DNS und Allowed IPs im Admin Panel gelten nur für neue Client-Konfigurationen. Bestehende Clients müssen ihre Konfiguration neu herunterladen, damit die Änderungen wirksam werden.

Web-UI zeigt “Setup Wizard” obwohl bereits konfiguriert

Das passiert, wenn das Volume nicht korrekt gemountet ist oder die SQLite-Datenbank fehlt. Prüfe:

1
ls -la ./data/

Es sollte mindestens eine db.sqlite-Datei vorhanden sein.

Hooks werden nicht ausgeführt

Nach dem Import aus der wg0.json sind die Hooks leer - das ist erwartetes Verhalten. v15 importiert nur die Client-Konfigurationen, nicht die Server-Einstellungen. Hooks manuell im Admin Panel unter Hooks nachtragen.


Fazit

wg-easy v15 ist ein großer Schritt nach vorn. Die Konfiguration über das Admin Panel ist deutlich komfortabler als das Jonglieren mit Environment-Variablen und bcrypt-Hashes. Die Migration selbst ist überschaubar - der wichtigste Punkt ist das Backup der wg0.json und das manuelle Nachtragen von DNS, Allowed IPs und Hooks nach dem Import.

Für neue Installationen gibt es keinen Grund mehr, bei v14 zu bleiben. Für bestehende Setups lohnt sich die Migration, sobald du 15 Minuten Zeit und ein aktuelles Backup hast.

Dieser Eintrag ist vom Autor unter CC BY 4.0 lizensiert.