wg-easy installeren: WireGuard met web UI in Docker
Installeer WireGuard met een webinterface via Docker Compose. Leer hoe u de NET_ADMIN flag en sysctls correct configureert voor mobiele QR-code onboarding op uw eigen VPS.
Wat u bouwt
wg-easy is WireGuard met een webinterface, uitgevoerd als één Docker-container. Het beheert de WireGuard-interface voor u en voegt een browser-UI toe voor het aanmaken van clients. Elke client die u aanmaakt krijgt een configuratiebestand en een QR-code, waardoor een telefoon verbinding maakt met de VPN door de camera op het scherm te richten.
De tunnel zelf is standaard WireGuard. De kernelmodule verplaatst de pakketten, waardoor de doorvoer gelijk is aan een handmatige configuratie. Wat u wint is het beheer van de client-levenscyclus: peers toevoegen, uitschakelen en verwijderen zonder via SSH een configuratiebestand te hoeven bewerken. Wat u opgeeft is de directe controle over die configuratie, wat het onderwerp is van de handmatige WireGuard-configuratie op een VPS.
U heeft een Linux-host nodig die u kunt beheren, zoals een KVM VPS, met een publiek IP-adres of een domeinnaam die daarnaar verwijst, een x86_64 of arm64 CPU, Docker Engine met de Compose-plugin, en root-toegang. Container-virtualisatie die de host-kernel deelt, zoals OpenVZ of LXC, kan de WireGuard-module meestal niet laden, waardoor de container de interface niet kan opstarten.
Versie 15 heeft de instellingen uit de environment verplaatst
De meeste handleidingen die u zult vinden, zijn geschreven voor wg-easy 14, waarbij u WG_HOST instelde op uw serveradres en PASSWORD_HASH op een bcrypt-hash van het beheerderswachtwoord, beide als environment variables. Versie 15 is een herschreven versie. De officiële migratienotities vermelden duidelijk dat v15 niet dezelfde environment variables gebruikt als v14, en dat de meeste daarvan zijn verplaatst naar het beheerderspaneel in de web-UI.
WG_HOST en PASSWORD_HASH doen daarom niets meer. Als u een oud compose-bestand kopieert, start de container, negeert deze regels en vraagt vervolgens om een beheerdersaccount aan te maken in de browser. Dat is geen bug. Het is de nieuwe installatieprocedure.
Sinds oktober 2026 is de major-tag die u moet vastzetten 15. Pin de major-versie in plaats van latest te gebruiken, omdat een major-upgrade het configuratieformaat op de schijf wijzigt en niet probleemloos teruggedraaid kan worden. De overstap naar 15 verving het JSON-bestand van v14 door een SQLite-database.
Het compose-bestand
Maak een map aan voor de stack en schrijf het officiële compose-bestand hierin. Dit is het upstream-bestand, waarbij de optionele regels die in commentaar staan zijn weggelaten.
sudo mkdir -p /etc/docker/containers/wg-easy
sudo curl -o /etc/docker/containers/wg-easy/docker-compose.yml \
https://raw.githubusercontent.com/wg-easy/wg-easy/master/docker-compose.ymlDe inhoud ziet er als volgt uit:
volumes:
etc_wireguard:
services:
wg-easy:
image: ghcr.io/wg-easy/wg-easy:15
container_name: wg-easy
networks:
wg:
ipv4_address: 10.42.42.42
ipv6_address: fdcc:ad94:bacf:61a3::2a
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:
driver: bridge
enable_ipv6: true
ipam:
driver: default
config:
- subnet: 10.42.42.0/24
- subnet: fdcc:ad94:bacf:61a3::/64etc_wireguard is een named volume dat de server-key en elke client die u aanmaakt bevat, opgeslagen in de SQLite-database wg-easy.db. Maak een back-up van dit volume, anders raakt u bij een rebuild al uw peers kwijt. Als u deze bestanden liever op het host-bestandssysteem ziet, vervang het dan door een bind mount. Lees eerst het verschil tussen bind mounts en named volumes voordat u dit doet, omdat de permissies zich anders gedragen.
Waarom NET_ADMIN, SYS_MODULE en de sysctls nodig zijn
Een container mag standaard de netwerkstack niet aanpassen, en elk van deze regels heft een specifieke blokkade op.
NET_ADMIN staat de container toe om de wg0 interface aan te maken, een adres toe te wijzen en routes te schrijven. Zonder deze optie start de container en stopt deze direct bij het initialiseren van de interface, omdat ip link add wg0 type wireguard de foutmelding Operation not permitted teruggeeft.
SYS_MODULE in combinatie met de read-only /lib/modules mount stelt de container in staat om de WireGuard kernelmodule te laden als de host deze nog niet heeft geladen. De module bevindt zich in de host-kernel, niet in de image, waardoor de host-directory zichtbaar moet zijn. Op een moderne kernel is de module meestal ingebouwd; u kunt dit controleren met sudo modprobe wireguard && echo ok op de host.
net.ipv4.ip_forward=1 zorgt ervoor dat de kernel pakketten doorstuurt die niet aan de machine zelf zijn geadresseerd. Zonder deze instelling maakt een client verbinding, slaagt de handshake, maar wordt elk pakket naar het internet genegeerd. Hierdoor treedt er een time-out op in ping 1.1.1.1, terwijl de VPN-verbinding er verbonden uitziet.
net.ipv4.conf.all.src_valid_mark=1 is de instelling die vaak voor verrassing zorgt. WireGuard markeert zijn eigen uitgaande pakketten zodat deze niet terug de tunnel in worden gerouteerd. Strikte reverse path filtering ziet een pakket waarvan het bronadres niet overeenkomt met de verwachte route en negeert dit. Deze sysctl instrueert de kernel om gemarkeerde pakketten te accepteren, wat voorkomt dat een volledige tunnel zichzelf blokkeert.
Start de service en maak het beheerdersaccount aan
cd /etc/docker/containers/wg-easy
sudo docker compose up -d
sudo docker compose logs -fGebruik docker compose up en docker compose down, niet start en stop. start herstart alleen de containers die al bestaan; wijzigingen in het compose-bestand, inclusief netwerk- en sysctl-instellingen, worden hierdoor niet toegepast. up daarentegen maakt gewijzigde containers opnieuw aan. Als u wilt dat de stack na een herstart weer actief is, voorziet restart: unless-stopped hierin, en het opstartgedrag van compose-services legt uit wat dat beleid wel en niet garandeert.
De web-UI luistert op TCP 51821. Bij het eerste bezoek verschijnt een installatiepagina waar u het beheerdersaccount aanmaakt en het hostadres bevestigt dat clients gebruiken om de server te bereiken. Dat hostadres komt in de Endpoint-regel van elke clientconfiguratie terecht, dus dit moet het publieke IP-adres of de DNS-naam van de VPS zijn. Als dit onjuist is, wijst de QR-code die u aan een telefoon geeft naar een onbereikbare locatie en wordt de handshake nooit voltooid.
Nog een punt over die poort: wg-easy biedt zelf geen TLS en weigert geen platte HTTP. Via http:// laadt de UI nog steeds, maar met een waarschuwingsbanner. De standaardinstelling wijzigt de sessie-cookie, die de Secure-vlag draagt. Een browser die de UI via platte HTTP op een publiek adres bereikt, verwerpt deze cookie, waardoor de login niet behouden blijft. INSECURE=true verwijdert die vlag en doet verder niets. Het plaatsen van een reverse proxy die TLS-termination afhandelt, zelfs met een zelfondertekend certificaat, vereist geen enkele aanpassing aan wg-easy.
Publiceer de UI-poort niet naar het internet
Het compose-bestand publiceert 51821 op elke interface. Dit is een inlogpagina voor een systeem dat uw verkeer kan routeren en deze mag niet openstaan voor de buitenwereld. Het publiceren van een poort in Docker schrijft regels naar de DOCKER-chain, die vóór ufw wordt geëvalueerd; een ufw deny-regel sluit deze poort dus niet af. Dit is een belangrijk aandachtspunt, en waarom Docker-gepubliceerde poorten ufw negeren legt dit volledig uit.
De eenvoudige oplossing is om de UI te binden aan localhost en deze te bereiken via een SSH-tunnel:
ports:
- "51820:51820/udp"
- "127.0.0.1:51821:51821/tcp"
environment:
- INSECURE=trueVoer vervolgens vanaf uw laptop uit:
ssh -L 51821:127.0.0.1:51821 youruser@your.server.addressOpen http://127.0.0.1:51821 in de browser op uw laptop. Het verkeer is versleuteld via SSH en de poort reageert op niemand anders. INSECURE=true verwijdert alleen de Secure-vlag van de sessie-cookie, zodat de browser uw login op dit platte HTTP-adres behoudt. Dit is veilig omdat de platte HTTP-verbinding de loopback-interface niet verlaat.
Open UDP 51820 en controleer beide firewalls
WireGuard zelf vereist dat UDP 51820 bereikbaar is vanaf het internet. Docker publiceert deze poort, maar veel providers plaatsen een externe netwerkfirewall voor de VPS waar Docker geen weet van heeft. Open de poort op beide locaties. Als u de host-firewall beheert met ufw, is de basis ufw-regels voor een VPS een snellere methode dan het handmatig schrijven van nftables.
Controleer of de container daadwerkelijk luistert:
sudo ss -ulnp | grep 51820U hoort een luisterende UDP-socket te zien. Als er niets op die regel staat, betekent dit dat de container de interface niet heeft geactiveerd, en sudo docker compose logs wg-easy zal de reden hiervoor aangeven.
Een client aanmaken en scannen met een telefoon
Maak in de UI een client aan en geef deze een naam die u later herkent, zoals het apparaat waarvoor deze bedoeld is. wg-easy wijst het eerstvolgende vrije tunneladres toe en genereert het sleutelpaar voor u. Elke clientrij biedt een QR-code en een downloadbaar .conf-bestand.
Installeer de officiële WireGuard-app op de telefoon, kies voor het toevoegen van een tunnel via een QR-code en richt de camera op de code op uw scherm. De tunnel verschijnt met de naam die u heeft ingevoerd. Schakel deze in; de clientrij in de UI begint nu overdrachtstellers en de tijd van de meest recente handshake weer te geven. Zodra een telefoon verbonden is met de tunnel, kan deze services bereiken die u nooit op internet heeft gepubliceerd. Zo kan een telefoon overal vandaan foto's uploaden naar een zelfgehoste fotoserver, zonder dat die server ook maar één poort open heeft staan naar de buitenwereld. Dezelfde methode werkt voor media; een Jellyfin-bibliotheek die is ingericht als een videotheek uit de jaren 90 is prettig om vanuit een hotelkamer te doorzoeken, terwijl deze net zo privé blijft als op uw LAN. Waarschuwingen werken via dezelfde tunnel de andere kant op, aangezien een zelfgehoste ntfy-server een bericht naar die telefoon kan pushen zodra een back-uptaak mislukt, zonder ooit een verzoek vanaf het openbare internet te beantwoorden.
Een client die na het inschakelen geen handshake toont, bereikt de server niet. Dit wijst op UDP 51820, hetzij bij de firewall van de provider, hetzij bij het endpoint-adres dat in de configuratie is vastgelegd. Een client die wel een handshake toont maar geen werkende internetverbinding heeft, wijst op problemen met forwarding of DNS.
Download op een desktop het .conf-bestand en importeer dit in de WireGuard-client in plaats van het opnieuw in te typen. De privésleutel in dat bestand wordt eenmalig gegenereerd en getoond. Behandel dit bestand zoals u een SSH-privésleutel behandelt.
Wanneer u de UI ontgroeit
wg-easy is het juiste hulpmiddel zolang uw peers mensen en telefoons zijn. De UI is sneller dan het bewerken van configuratiebestanden, en het intrekken van de toegang voor een verloren telefoon kost slechts één klik.
U zult tegen de grenzen aanlopen wanneer u iets wilt dat de UI niet ondersteunt. Site-to-site routing, waarbij een AllowedIPs van een peer een volledig extern subnet beslaat in plaats van een enkel adres, is meestal de eerste barrière. Split tunnels met routeringsregels per peer, of een configuratie die door uw provisioning-tool wordt gegenereerd, zijn de volgende. Op dat punt is de handmatige configuratie niet moeilijker, maar simpelweg anders, en de handleiding voor standaard WireGuard toont dezelfde tunnel opgebouwd vanuit wg0.conf. Als u liever helemaal geen control plane draait, behandelt WireGuard vergeleken met Tailscale de beheerde optie. Of dat een eerlijke ruil is, hangt af van wat de coördinatieserver daadwerkelijk kan bereiken, en het vertrouwensmodel van Tailscale is het lezen waard voordat u uw netwerk uit handen geeft. Kosten zijn meestal de volgende vraag, en wat het gratis abonnement van Tailscale werkelijk dekt is maximaal zes gebruikers zonder kosten, uitsluitend voor niet-commercieel gebruik; een huishouden betaalt er dus niets voor, terwijl een klein zakelijk team er niet voor in aanmerking komt, ongeacht het aantal personen. Voorbij dat punt telt de facturatie gebruikers in plaats van apparaten, wat een andere factuurstructuur is dan een VPS waar u al voor betaalt, dus wat Tailscale kost zodra u het gratis abonnement ontgroeit is het getal om te controleren voordat u een team migreert. De volledige tunnel die u zojuist heeft gebouwd heeft daar een direct equivalent, aangezien het adverteren van de VPS als een Tailscale exit node u dezelfde route naar buiten geeft via de server, goedgekeurd in de beheerconsole in plaats van geschreven in elke clientconfiguratie. De subnet-barrière heeft ook een equivalent, omdat het adverteren van een volledig privénetwerk vanaf de VPS dat netwerk beschikbaar stelt aan elk apparaat in de tailnet zonder de bewerkingen per peer in AllowedIPs die u wegstuurden van de UI. Als u dat dashboard en de automatische mesh-routering wilt, maar niet de coördinatieserver van iemand anders, houdt het draaien van uw eigen NetBird-server op een VPS de control plane op hardware die u bezit, ten koste van de DNS- en TLS-configuratie waar wg-easy nooit om vroeg.
Als de compose-syntaxis hierboven het onbekende deel was in plaats van het WireGuard-deel, legt Docker Compose basisprincipes op een VPS het bestandsformaat en de dagelijkse commando's uit.
FAQ
Waarom negeert wg-easy mijn WG_HOST en PASSWORD_HASH?
Deze variabelen hoorden bij wg-easy 14. Versie 15 is een herschreven versie en de ontwikkelaars hebben bijna alle configuratie verplaatst naar het beheerderspaneel in de web-UI. De container leest beide variabelen niet; hij start normaal op en vraagt u bij het eerste bezoek om een beheerdersaccount aan te maken. Stel het hostadres voor clients in op die installatiepagina.
Heb ik SYS_MODULE nodig als mijn kernel al WireGuard heeft?
Nee. SYS_MODULE en de /lib/modules-mount zijn aanwezig zodat de container de module kan laden als de host dat nog niet heeft gedaan. Op een host waar sudo modprobe wireguard al slaagt, wordt de capability niet gebruikt. Het verwijderen ervan is een goede beveiligingsstap, en NET_ADMIN is in beide gevallen vereist.
De client maakt verbinding, maar er is geen internet. Wat is er mis?
Een handshake zonder verkeer duidt bijna altijd op een probleem met forwarding. Controleer of net.ipv4.ip_forward=1 en net.ipv4.conf.all.src_valid_mark=1 nog in het compose-bestand staan, aangezien handmatige kopieën deze vaak verliezen. Als forwarding is ingeschakeld, controleer dan de DNS-server die de client heeft ontvangen. Een tunnel die al het verkeer door de VPN stuurt maar verwijst naar een DNS-server die niet langer bereikbaar is, lijkt in een browser op een verbroken verbinding.
Hoe maak ik back-ups van mijn clients?
Alles bevindt zich in het etc_wireguard named volume. In versie 15 betekent dit een SQLite-database op /etc/wireguard/wg-easy.db binnen de container. De v15-documentatie beschrijft geen back-up- of exportfunctie, dus maak een back-up van het volume zelf: voer docker compose down uit, kopieer de inhoud van het volume naar een locatie buiten de server en start de stack vervolgens weer op met docker compose up -d. Doe dit vóór elke upgrade. Het wg0.json-bestand waar oudere handleidingen over spreken, hoort bij wg-easy 14. Dat is wat de v14-back-upknop exporteert, en in v15 is het enige nut ervan het bestand dat u uploadt in de installatiewizard wanneer u een oude installatie migreert.
Kan ik wg-easy achter een reverse proxy draaien?
Ja. Plaats de proxy vóór de web-UI-poort, handel daar de TLS-termination af en laat INSECURE ongewijzigd. De container accepteert de platte HTTP-verbinding van de proxy zoals deze is, en de officiële Caddy- en Traefik-voorbeelden stellen geen INSECURE-variabele in. Omdat de browser HTTPS spreekt met de proxy, wilt u dat de sessie-cookie de Secure-vlag behoudt; dit is het enige dat INSECURE=true verwijdert. Houd UDP 51820 direct gepubliceerd, omdat het VPN-verkeer UDP is en niet via een HTTP-proxy verloopt.