wg-easy mit Docker installieren: WireGuard-VPN mit Web-UI in einem Container
wg-easy kombiniert WireGuard-Server und browserbasierte Admin-Oberfläche in einem einzigen Docker-Container. Diese Anleitung zeigt dir, wie du ein vollständiges VPN mit Benutzerverwaltung, QR-Codes und Echtzeit-Statistiken in unter 20 Minuten aufbaust – plattformneutral auf jedem Linux-Host.

WireGuard gilt als das modernste und schnellste VPN-Protokoll – doch die manuelle Einrichtung über die Linux-Kommandozeile schreckt viele ab. wg-easy löst dieses Problem radikal: Ein einziger Docker-Container liefert gleichzeitig den WireGuard-Server, eine browserbasierte Verwaltungsoberfläche und eine eingebettete SQLite-Datenbank. Clients anlegen, QR-Codes scannen, Verbindungen überwachen – alles im Browser, ohne ein einziges wg-Kommando. Mit über 26.000 GitHub-Stars und 28 Millionen Container-Pulls ist wg-easy das meistgenutzte WireGuard-Frontend für Docker. Diese Anleitung richtet sich an Heimanwender, Selfhoster und KMU-Admins, die ein produktionsreifes VPN ohne Datenbank-Overhead und ohne WireGuard-CLI-Kenntnisse betreiben wollen.
Voraussetzungen
- Linux-Host mit Kernel ≥ 5.6 (Ubuntu 20.04+, Debian 11+, Raspberry Pi OS Bullseye+) – das WireGuard-Kernelmodul muss vorhanden sein.
- Docker Engine 20.10+ und das Docker Compose v2-Plugin installiert. Falls noch nicht vorhanden, hilft die Grundlagenanleitung Docker und Docker Compose auf Linux installieren.
- Öffentliche IP-Adresse oder DynDNS-Hostname für den Parameter
WG_HOST– ohne diesen Wert können sich keine Clients verbinden. - Zugang zur Router-/Firewall-Konfiguration, um UDP-Port 51820 an die interne IP des Docker-Hosts weiterzuleiten.
- Minimum: 1 CPU-Kern, 256 MB RAM, 500 MB Speicherplatz für das Volume.
- Optional: Reverse-Proxy (Nginx, Traefik, Caddy) für HTTPS-Absicherung der Web-UI – empfohlen bei öffentlicher Erreichbarkeit.
Eckdaten auf einen Blick
| Eigenschaft | Wert |
|---|---|
| Image | ghcr.io/wg-easy/wg-easy:15 |
| Aktuelle Version | v15.3.0 (Mai 2026) |
| Port VPN-Tunnel | 51820/udp |
| Port Web-UI | 51821/tcp |
| Volume (Pflicht) | etc_wireguard:/etc/wireguard |
| Bind Mount | /lib/modules:/lib/modules:ro |
| Architektur | amd64, arm64, arm/v7 |
| Datenbank | SQLite (eingebettet, kein externer Dienst) |
| Lizenz | AGPL-3.0-only |
Wichtige Umgebungsvariablen
| Variable | Pflicht | Beschreibung |
|---|---|---|
WG_HOST | Ja | Öffentliche IP oder Domain; wird als Endpoint in Client-Configs eingetragen |
PASSWORD_HASH | Empfohlen | bcrypt-Hash des Admin-Passworts; $ in compose.yaml doppeln ($$) |
TZ | Nein | Zeitzone (z. B. Europe/Berlin) |
LANG | Nein | UI-Sprache (de, en usw.) |
WG_DEFAULT_DNS | Nein | DNS für VPN-Clients (Standard: 1.1.1.1) |
WG_ALLOWED_IPS | Nein | Gepushte Routen; Standard 0.0.0.0/0 = Full-Tunnel |
WG_PERSISTENT_KEEPALIVE | Nein | Keepalive in Sekunden; 25 empfohlen hinter NAT |
UI_TRAFFIC_STATS | Nein | true aktiviert Echtzeit-Statistiken in der UI |
Schritt 1: Projektordner anlegen
Lege einen dedizierten Ordner für das wg-easy-Projekt an. Alle Konfigurationsdateien kommen hier hinein – das Volume selbst wird Docker-intern verwaltet.
mkdir -p /opt/wg-easy
cd /opt/wg-easyVerifizieren: Der Ordner existiert und du befindest dich darin.
pwd
# Erwartete Ausgabe: /opt/wg-easySchritt 2: bcrypt-Passwort-Hash generieren
wg-easy verwendet seit v14 ausschließlich PASSWORD_HASH mit einem bcrypt-Hash – das alte PASSWORD (Klartext) ist deprecated. Ohne Passwort ist die Web-UI ohne jede Authentifizierung erreichbar, was auf einem öffentlich zugänglichen Server ein erhebliches Sicherheitsrisiko darstellt.
Führe folgenden Befehl aus und ersetze MEIN_SICHERES_PASSWORT durch dein gewünschtes Admin-Passwort:
docker run --rm ghcr.io/wg-easy/wg-easy:15 \
node -e "const b=require('bcryptjs');console.log(b.hashSync('MEIN_SICHERES_PASSWORT',12).replace(/\$/g,'\$\$'))"Die Ausgabe sieht etwa so aus:
$$2y$$12$$abcdefghijklmnopqrstuuXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXDiesen Hash trägst du direkt in die .env-Datei ein (dort sind keine doppelten $ nötig – das Escaping ist nur in compose.yaml direkt erforderlich, nicht in .env-Dateien).
Verifizieren: Die Ausgabe beginnt mit $$2y$$ oder $$2b$$ und enthält einen langen Hash-String. Falls der Befehl mit einem Fehler abbricht, prüfe, ob das Image bereits lokal vorhanden ist (docker images | grep wg-easy) und ob die Anführungszeichen korrekt übergeben werden.
Schritt 3: .env-Datei mit Secrets anlegen
Die .env-Datei trennt Konfigurationswerte vom Compose-File und verhindert, dass Passwörter versehentlich in Versionskontrollsysteme gelangen. Erstelle sie im Projektordner:
# /opt/wg-easy/.env
# PFLICHT: Öffentliche IP oder DynDNS-Hostname des Servers
WG_HOST=vpn.example.com
# SICHERHEIT: bcrypt-Hash des Admin-Passworts
# In .env KEINE doppelten $$; der Wert direkt aus Schritt 2 einfügen
PASSWORD_HASH=$2y$12$abcdefghijklmnopqrstuuXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
# Optional: Zeitzone und UI-Sprache
TZ=Europe/Berlin
LANG=de
# Optional: DNS für VPN-Clients (Standard: 1.1.1.1)
WG_DEFAULT_DNS=1.1.1.1
# Optional: Full-Tunnel (alle Routen) oder Split-Tunnel (z. B. 192.168.0.0/24)
WG_ALLOWED_IPS=0.0.0.0/0, ::/0
# Optional: Keepalive für NAT-Verbindungen (25 Sekunden empfohlen)
WG_PERSISTENT_KEEPALIVE=25
# Optional: Echtzeit-Statistiken in der UI
UI_TRAFFIC_STATS=trueSchütze die Datei vor Lesezugriff durch andere Systembenutzer:
chmod 600 /opt/wg-easy/.envVerifizieren: Die Datei hat die Berechtigungen 600 und enthält deinen echten WG_HOST-Wert.
ls -la /opt/wg-easy/.env
# Erwartete Ausgabe: -rw------- 1 root root ... .envSchritt 4: compose.yaml erstellen
Erstelle die Compose-Konfiguration. Wichtig: Der Image-Tag :15 ist der vom offiziellen Projekt empfohlene Major-Tag für stabile Deployments. Der Tag :latest kann laut offizieller Dokumentation Breaking Changes enthalten und sollte vermieden werden.
# /opt/wg-easy/compose.yaml
services:
wg-easy:
image: ghcr.io/wg-easy/wg-easy:15
container_name: wg-easy
environment:
- WG_HOST=${WG_HOST}
- PASSWORD_HASH=${PASSWORD_HASH}
- TZ=${TZ:-Europe/Berlin}
- LANG=${LANG:-de}
- WG_DEFAULT_DNS=${WG_DEFAULT_DNS:-1.1.1.1}
- WG_ALLOWED_IPS=${WG_ALLOWED_IPS:-0.0.0.0/0, ::/0}
- WG_PERSISTENT_KEEPALIVE=${WG_PERSISTENT_KEEPALIVE:-25}
- UI_TRAFFIC_STATS=${UI_TRAFFIC_STATS:-true}
volumes:
- etc_wireguard:/etc/wireguard
- /lib/modules:/lib/modules:ro
ports:
- "51820:51820/udp"
- "51821:51821/tcp"
restart: unless-stopped
cap_add:
- NET_ADMIN
- SYS_MODULE
sysctls:
- net.ipv4.ip_forward=1
- net.ipv4.conf.all.src_valid_mark=1
- net.ipv6.conf.all.disable_ipv6=0
- net.ipv6.conf.all.forwarding=1
- net.ipv6.conf.default.forwarding=1
networks:
wg:
ipv4_address: 10.42.42.42
ipv6_address: fdcc:ad94:bacf:61a3::2a
volumes:
etc_wireguard:
networks:
wg:
driver: bridge
enable_ipv6: true
ipam:
driver: default
config:
- subnet: 10.42.42.0/24
- subnet: fdcc:ad94:bacf:61a3::/64Ein paar Erklärungen zu den sicherheitskritischen Parametern: cap_add: NET_ADMIN erlaubt dem Container, Netzwerkschnittstellen und Firewall-Regeln (iptables) zu verwalten – ohne diese Capability startet WireGuard nicht. SYS_MODULE ermöglicht das Laden des WireGuard-Kernelmoduls vom Host. Die sysctls-Einträge aktivieren IP-Forwarding auf IPv4 und IPv6, was zwingend notwendig ist, damit VPN-Clients ihren Traffic über den Server ins Internet leiten können. Das Bind Mount /lib/modules:/lib/modules:ro gibt dem Container schreibgeschützten Zugriff auf die Kernel-Module des Hosts.
Verifizieren: Das Compose-File ist syntaktisch korrekt.
docker compose -f /opt/wg-easy/compose.yaml config --quiet
# Keine Ausgabe = kein SyntaxfehlerSchritt 5: Container starten
Starte wg-easy im Hintergrund. Verwende stets docker compose up -d und docker compose down – niemals start/stop, da diese den Container nicht neu initialisieren und WireGuard-Schnittstellen in einem inkonsistenten Zustand hinterlassen können.
cd /opt/wg-easy
docker compose up -dDocker lädt das Image von der GitHub Container Registry (ghcr.io) und startet den Container. Beim ersten Start richtet wg-easy automatisch die WireGuard-Schnittstelle ein und legt die SQLite-Datenbank an.
Verifizieren: Container-Status und Logs prüfen.
docker compose ps
# Erwartete Ausgabe: STATUS = Up (oder running)
# NAME IMAGE STATUS PORTS
# wg-easy ghcr.io/wg-easy/wg-easy:15 Up ... 0.0.0.0:51820->51820/udp, 0.0.0.0:51821->51821/tcp
docker compose logs --tail=30
# Erwartete Ausgabe: Keine ERROR-Einträge; Zeilen wie:
# [wg-easy] WireGuard interface started
# [wg-easy] Web UI listening on port 51821Falls der Container sofort wieder stoppt, lohnt ein Blick in die vollständigen Logs: docker compose logs wg-easy. Die häufigste Ursache ist ein fehlender oder falsch escapeter PASSWORD_HASH – siehe Troubleshooting-Abschnitt.
Schritt 6: Router-Portweiterleitung einrichten
Damit VPN-Clients von außen eine Verbindung aufbauen können, muss UDP-Port 51820 am Router an die interne IP des Docker-Hosts weitergeleitet werden. TCP-Port 51821 (Web-UI) sollte nicht direkt ans Internet freigegeben werden – stattdessen über einen HTTPS-Reverse-Proxy absichern (siehe Schritt 8).
- In der Router-Oberfläche (FritzBox: „Internet → Freigaben → Portfreigaben"; OPNsense: „Firewall → NAT → Port Forward") eine neue Regel anlegen.
- Protokoll: UDP, externer Port: 51820, internes Ziel: IP des Docker-Hosts, interner Port: 51820.
- Wenn du eine dynamische IP-Adresse hast, richte einen DynDNS-Dienst ein und trage den Hostnamen in
WG_HOSTein.
Verifizieren: UDP-Erreichbarkeit von außen testen (z. B. mit einem Mobilgerät im Mobilfunknetz).
# Auf dem Server: WireGuard-Interface ist aktiv
docker exec wg-easy wg show
# Erwartete Ausgabe: interface: wg0, public key: ..., listening port: 51820Schritt 7: Ersten VPN-Client in der Web-UI anlegen
Öffne im Browser http://<IP-des-Servers>:51821. Du wirst nach dem Admin-Passwort gefragt, das du in Schritt 2 festgelegt hast. Nach dem Login siehst du das Dashboard – noch ohne Clients.
- Klicke auf „Neuen Client hinzufügen" (oder das +-Symbol).
- Vergib einen Namen (z. B. „iPhone Marcel" oder „Laptop Büro").
- wg-easy generiert automatisch ein Schlüsselpaar und eine Client-Konfiguration.
- Klicke auf das QR-Code-Symbol, um den QR-Code anzuzeigen.
- Scanne den QR-Code in der offiziellen WireGuard-App (Android/iOS) – fertig.
Für Desktop-Clients (Windows/macOS/Linux) kannst du die Konfigurationsdatei herunterladen und in den WireGuard-Client importieren.
Verifizieren: Verbinde dich vom Client und prüfe die Verbindungsstatistiken in der Web-UI.
# Auf dem Server: aktive Peers anzeigen
docker exec wg-easy wg show
# Nach erfolgreichem Verbindungsaufbau erscheint der Client unter "peer:" mit
# "latest handshake:" und "transfer:" WertenSchritt 8: HTTPS-Reverse-Proxy (empfohlen)
Die Web-UI über HTTP zugänglich zu lassen ist im lokalen Heimnetz akzeptabel, aber für jeden Server mit öffentlicher IP ein unnötiges Risiko. Richte einen Reverse-Proxy vor Port 51821 ein. Eine ausführliche Anleitung dazu findest du unter Caddy als Reverse Proxy mit automatischem HTTPS einrichten oder Traefik als Docker-Reverse-Proxy mit automatischem HTTPS.
Wichtige Punkte beim Betrieb hinter einem Reverse-Proxy:
WG_HOSTmuss auf die Domain zeigen, über die der Proxy erreichbar ist (z. B.vpn.example.com).- Port 51820/udp muss direkt am Router weitergeleitet werden – UDP kann nicht über HTTP-Proxies getunnelt werden.
- Port 51821/tcp kann nach der Proxy-Einrichtung aus den öffentlichen Portfreigaben entfernt werden.
Verifizieren: HTTPS-Aufruf der Web-UI und Zertifikat prüfen.
curl -I https://vpn.example.com
# Erwartete Ausgabe: HTTP/2 200 oder 302 (Login-Redirect)Schritt 9: Updates und Backup
Alle Daten (WireGuard-Konfiguration, Schlüssel, SQLite-Datenbank mit allen Clients) liegen im Docker-Volume etc_wireguard. Ein Update läuft ohne Datenverlust ab – trotzdem solltest du vor Major-Updates ein Backup erstellen.
Volume sichern:
docker run --rm \
-v etc_wireguard:/data \
-v $(pwd):/backup \
alpine tar czf /backup/wg-backup-$(date +%Y%m%d).tar.gz /data
# Erstellt wg-backup-20260611.tar.gz im aktuellen VerzeichnisUpdate:
cd /opt/wg-easy
docker compose pull
docker compose up -d
# Docker ersetzt den Container automatisch mit dem neuen ImageFür automatische Update-Benachrichtigungen lohnt ein Blick auf Docker-Container automatisch aktualisieren: Diun, WUD und Renovate im Vergleich.
Verifizieren: Neue Version ist aktiv.
docker compose ps
docker inspect wg-easy --format '{{.Config.Image}}'
# Ausgabe: ghcr.io/wg-easy/wg-easy:15Troubleshooting / Typische Fehler
- „Cannot find device wg0" / WireGuard startet nicht: Die Linux-Capabilities
NET_ADMINundSYS_MODULEfehlen im Compose-File. Prüfe dencap_add-Block incompose.yamlund starte den Container neu. - Clients verbinden sich, aber kein Internetzugriff: IP-Forwarding ist nicht aktiv. Prüfe mit
docker exec wg-easy sysctl net.ipv4.ip_forward– der Wert muss1sein. Bei anhaltenden Problemen zusätzlich dauerhaft in/etc/sysctl.confsetzen:net.ipv4.ip_forward=1. - Web-UI ohne Passwort erreichbar trotz gesetztem PASSWORD_HASH: Dollar-Zeichen im Hash sind in
compose.yamlnicht korrekt escaped. Jedes$muss zu$$werden. Verwende stattdessen die.env-Datei – dort ist kein Escaping nötig. - Clients können sich nicht verbinden / Endpoint nicht erreichbar:
WG_HOSTzeigt auf eine interne oder falsche IP. SetzeWG_HOSTauf die öffentliche IP-Adresse oder den DynDNS-Hostnamen. - Port 51820 von außen nicht erreichbar: UDP-Portweiterleitung am Router fehlt oder ist für TCP statt UDP konfiguriert. WireGuard verwendet ausschließlich UDP.
- „/lib/modules: no such file or directory": Auf manchen VMs oder gehosteten Containern existiert dieses Verzeichnis nicht. Prüfe mit
lsmod | grep wireguard, ob das Modul bereits geladen ist – dann kann der Bind-Mount-Eintrag auscompose.yamlentfernt werden. - Web-UI reagiert nicht nach Major-Update: Mögliche Schema-Änderungen in der SQLite-DB. Vor dem Update immer ein Volume-Backup erstellen (siehe Schritt 9) und bei Problemen aus dem Backup wiederherstellen.
- Image von Docker Hub (weejewel/wg-easy): Das alte Docker-Hub-Image wird nicht mehr gepflegt und enthält bekannte Sicherheitslücken. Ausschließlich
ghcr.io/wg-easy/wg-easy:15verwenden. - Falsches Netzwerkinterface (WG_DEVICE): Der Standard-Wert
eth0stimmt nicht immer mit dem Host-Interface überein. Prüfe mitip link showund setzeWG_DEVICEentsprechend (z. B.ens3,enp0s3).
Häufige Fragen
Muss WireGuard auf dem Host installiert sein?
Nein. Das Docker-Image bringt alles Notwendige mit. Genutzt wird lediglich das WireGuard-Kernelmodul des Host-Linux-Systems, das in Ubuntu 20.04+, Debian 11+ und Raspberry Pi OS Bullseye+ bereits eingebaut ist. Das Userspace-Tool wireguard-tools muss nicht installiert sein.
Wie generiere ich den bcrypt-Hash für PASSWORD_HASH?
Mit diesem einzeiligen Befehl, den du direkt auf dem Server ausführst:
docker run --rm ghcr.io/wg-easy/wg-easy:15 \
node -e "const b=require('bcryptjs');console.log(b.hashSync('MEIN_PASSWORT',12).replace(/\$/g,'\$\$'))"Den ausgegebenen Hash in die .env-Datei eintragen (dort ohne doppelte $). Bei direkter Verwendung in compose.yaml müssen alle $-Zeichen verdoppelt werden.
Funktioniert wg-easy auf einem Raspberry Pi?
Ja. Das Image unterstützt linux/arm64 und linux/arm/v7 und läuft auf allen Pi-Generationen ab dem Raspberry Pi 2. Raspberry Pi OS Bullseye (und neuer) enthält das WireGuard-Kernelmodul bereits standardmäßig.
Wie sichere ich das wg-easy-Volume?
Das Volume etc_wireguard enthält die WireGuard-Konfiguration, alle Schlüssel und die SQLite-Datenbank mit sämtlichen Clients. Der Backup-Befehl aus Schritt 9 erstellt ein komprimiertes Archiv. Automatisiere ihn per Cron oder integriere ihn in deine bestehende 3-2-1-Backup-Strategie.
Kann ich wg-easy hinter Nginx/Traefik/Caddy betreiben?
Ja, und es wird ausdrücklich empfohlen. Den TCP-Port 51821 nicht direkt ans Internet exponieren, sondern den Reverse-Proxy mit TLS-Terminierung davorschalten. WG_HOST muss auf die Domain zeigen. Port 51820/udp muss weiterhin direkt am Router weitergeleitet werden – UDP lässt sich nicht über HTTP-Proxies tunneln.
Was passiert mit den Clients beim Container-Update?
Alle Daten liegen im Volume und bleiben bei einem Update vollständig erhalten. Das Vorgehen ist: docker compose pull, dann docker compose up -d. Docker ersetzt den Container automatisch, das Volume bleibt unberührt. Vor Major-Version-Upgrades (z. B. von v14 auf v15) immer ein Volume-Backup anlegen.
Welche Features bietet die Web-UI?
Die aktuelle Version v15 bietet: Client erstellen, bearbeiten, deaktivieren und löschen; QR-Code anzeigen und als Datei herunterladen; Echtzeit-Verbindungsstatistiken (Datenvolumen, letzte Verbindungszeit); Zwei-Faktor-Authentifizierung (2FA); Prometheus-Metriken unter /metrics/prometheus; IPv6-Unterstützung; über 21 Sprachen – einstellbar über die LANG-Variable.
Fazit
wg-easy ist die pragmatischste Antwort auf die Frage, wie man WireGuard produktionsreif betreibt, ohne sich durch die WireGuard-Manpage kämpfen zu müssen. Ein Container, ein Volume, zwei Ports – fertig ist ein VPN, das sich auch Teammitglieder ohne Linux-Kenntnisse selbst einrichten können. Die Stärken liegen klar in der einfachen Client-Verwaltung über die Web-UI, der eingebetteten SQLite-Datenbank ohne externen Dienst und der breiten Architektur-Unterstützung von x86-Servern bis zum Raspberry Pi. Der einzige echte Trade-off: Wer komplexe Multi-Site-Topologien oder Enterprise-Nutzerverwaltung mit LDAP benötigt, stößt an die Grenzen dieses bewusst simplen Ansatzes und sollte dann eher zu Headscale für ein Tailscale-kompatibles Mesh-VPN greifen. Für Homeoffice-VPNs, Site-to-Site-Verbindungen im kleinen Rahmen und den persönlichen Self-Hosting-Stack ist wg-easy jedoch kaum zu schlagen.
Weiterführende Anleitungen und Quellen
- WireGuard-VPN für Homeoffice und Standortvernetzung einrichten – manuelle WireGuard-Konfiguration ohne Docker
- Headscale selbst hosten: Tailscale-Mesh-VPN ohne Cloud-Abhängigkeit für KMU – für komplexere Mesh-Topologien
- Docker und Docker Compose auf Linux installieren – falls Docker noch nicht eingerichtet ist
- Caddy als Reverse Proxy mit automatischem HTTPS einrichten – HTTPS für die wg-easy Web-UI
- 3-2-1-Backup-Strategie umsetzen – Volume-Backups in eine Gesamtstrategie einbetten
Offizielle Quellen: wg-easy auf GitHub (README, compose.yaml, Releases) · Offizielle Dokumentation v15