wg-easy: WireGuard con interfaccia web in Docker
Configura wg-easy con Docker Compose: porte, NET_ADMIN, sysctl indispensabili e codici QR per collegare rapidamente i telefoni alla VPN.
Cosa stai configurando
wg-easy è WireGuard con un'interfaccia web, eseguito in un unico container Docker. Gestisce l'interfaccia WireGuard e aggiunge un'interfaccia utente web per creare i client. Ogni client creato riceve un file di configurazione e un codice QR. In questo modo, per collegare un telefono alla VPN basta inquadrare lo schermo con la fotocamera.
Il tunnel utilizza il normale WireGuard. Il modulo del kernel gestisce l'inoltro dei pacchetti, quindi il throughput è lo stesso di una configurazione scritta manualmente. Il vantaggio riguarda la gestione del ciclo di vita dei client: puoi aggiungere, disabilitare ed eliminare i peer senza modificare un file di configurazione tramite SSH. Lo svantaggio è la minore possibilità di controllare direttamente quel file. Questo aspetto è illustrato in la configurazione manuale di WireGuard su un VPS.
Servono un VPS KVM con un indirizzo IPv4 pubblico, Docker Engine con il plugin Compose e l'accesso root. La virtualizzazione dei container che condivide il kernel dell'host, come OpenVZ o LXC, di norma non consente di caricare il modulo WireGuard. Di conseguenza, il container non riesce ad attivare l'interfaccia.
La versione 15 ha spostato le impostazioni fuori dall'ambiente
La maggior parte delle guide disponibili è stata scritta per wg-easy 14, dove si impostavano WG_HOST sull'indirizzo del server e PASSWORD_HASH su un hash bcrypt della password amministrativa, entrambe come variabili d'ambiente. La versione 15 è una riscrittura. Le note ufficiali sulla migrazione indicano chiaramente che v15 non usa le stesse variabili d'ambiente di v14 e che la maggior parte di queste impostazioni è stata spostata nel pannello di amministrazione dell'interfaccia web.
Di conseguenza, WG_HOST e PASSWORD_HASH non hanno più alcun effetto. Se si copia un vecchio file compose, il container si avvia, ignora quelle righe e chiede quindi di creare un account amministrativo nel browser. Non è un bug. È il nuovo flusso di configurazione.
A luglio 2026 il tag principale da fissare è 15. Fissare la versione principale invece di usare latest, perché un aggiornamento della versione principale modifica il formato della configurazione su disco e non consente un rollback pulito.
Il file compose
Creare una directory per lo stack e inserirvi il file compose ufficiale. È il file upstream, senza modifiche.
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.ymlIl contenuto è il seguente:
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 è un volume denominato che contiene la chiave del server e ogni client creato. Eseguire il backup di questo volume, altrimenti una ricostruzione elimina tutti i peer. Se si preferisce visualizzare questi file nel filesystem dell'host, sostituirlo con un bind mount e leggere prima la differenza tra bind mount e volumi denominati, perché i permessi vengono gestiti in modo diverso.
Perché richiede NET_ADMIN, SYS_MODULE e i sysctl
Per impostazione predefinita, un container non può modificare lo stack di rete. Ognuna di queste righe rimuove un blocco specifico.
NET_ADMIN consente al container di creare l’interfaccia wg0, assegnarle un indirizzo e scrivere le route. Senza questa capability, il container si avvia e poi si arresta durante l’attivazione dell’interfaccia, perché ip link add wg0 type wireguard restituisce Operation not permitted.
SYS_MODULE e il mount in sola lettura di /lib/modules consentono al container di caricare il modulo kernel WireGuard se l’host non lo ha già caricato. Il modulo risiede nel kernel dell’host, non nell’immagine. Per questo la directory dell’host deve essere visibile. Nei kernel moderni il modulo è in genere integrato. Puoi verificarlo eseguendo sudo modprobe wireguard && echo ok sull’host.
net.ipv4.ip_forward=1 indica al kernel di inoltrare i pacchetti che non sono destinati direttamente al sistema. Senza questa impostazione, il client si connette e l’handshake riesce, ma tutti i pacchetti diretti verso Internet vengono scartati. Di conseguenza ping 1.1.1.1 va in timeout mentre la VPN risulta connessa.
net.ipv4.conf.all.src_valid_mark=1 è l’impostazione che sorprende molti utenti. WireGuard contrassegna i propri pacchetti in uscita per impedire che vengano instradati nuovamente nel tunnel. Il reverse path filtering in modalità strict rileva un pacchetto il cui indirizzo sorgente non corrisponde alla route prevista e lo scarta. Questo sysctl indica al kernel di accettare i pacchetti contrassegnati, impedendo così che un full tunnel interrompa il proprio traffico.
Avviare il servizio e creare l'account amministratore
cd /etc/docker/containers/wg-easy
sudo docker compose up -d
sudo docker compose logs -fUsare docker compose up e docker compose down, non start e stop. Il progetto upstream avverte che start su un container creato con impostazioni diverse lascia la rete in uno stato incoerente. Se si vuole ripristinare lo stack dopo un riavvio, restart: unless-stopped lo gestisce già; il comportamento all'avvio dei servizi compose spiega cosa garantisce e cosa non garantisce questa policy.
L'interfaccia Web è in ascolto sulla porta TCP 51821. Al primo accesso viene visualizzata una pagina di configurazione in cui creare l'account amministratore e confermare l'indirizzo dell'host che i client useranno per raggiungere il server. Questo indirizzo viene inserito nella riga Endpoint di ogni configurazione client, quindi deve essere l'indirizzo IP pubblico o il nome DNS del VPS. Se è errato, il codice QR fornito a un telefono punta a una destinazione non raggiungibile e l'handshake non viene mai completato.
Un'ultima considerazione su questa porta: wg-easy 15 rifiuta HTTP in chiaro a meno che non si imposti INSECURE=true. È possibile raggiungerlo tramite HTTPS con un certificato non attendibile oppure terminare TLS su un reverse proxy davanti al servizio. Con le impostazioni predefinite, invece, non è possibile raggiungerlo tramite http://.
Non pubblicare la porta dell'interfaccia web su Internet
Il file Compose pubblica la porta 51821 su tutte le interfacce. È una pagina di accesso per un sistema in grado di instradare il traffico e non dovrebbe essere esposta a Internet. La pubblicazione di una porta in Docker inserisce regole nella catena DOCKER, che viene valutata prima di ufw; una regola di diniego in ufw, quindi, non la chiude. Questo comportamento merita di essere compreso separatamente; perché le porte pubblicate da Docker ignorano ufw lo descrive in dettaglio.
La soluzione più semplice consiste nel collegare l'interfaccia web al loopback e accedervi tramite un tunnel SSH:
ports:
- "51820:51820/udp"
- "127.0.0.1:51821:51821/tcp"
environment:
- INSECURE=truePoi, dal laptop:
ssh -L 51821:127.0.0.1:51821 youruser@your.server.addressApri http://127.0.0.1:51821 nel browser del laptop. Il traffico è cifrato da SSH, la porta non risponde a nessun altro host e INSECURE=true è sicuro in questo caso perché il collegamento HTTP non cifrato non esce mai dall'interfaccia di loopback.
Apri UDP 51820 e controlla entrambi i firewall
WireGuard deve essere raggiungibile da Internet tramite UDP 51820. Docker pubblica questa porta, ma molti provider collocano un firewall di rete separato davanti al VPS, che Docker non può gestire. Apri la porta in entrambi i punti. Se gestisci il firewall dell'host con ufw, le regole ufw di base per un VPS sono più semplici che scrivere manualmente regole nftables.
Verifica che il container sia effettivamente in ascolto:
sudo ss -ulnp | grep 51820Dovresti vedere un socket UDP in ascolto. Se la riga non mostra alcun risultato, il container non ha attivato l'interfaccia e sudo docker compose logs wg-easy indicherà il motivo.
Creare un client e scansionarlo con un telefono
Nell’interfaccia create un client e assegnategli un nome che possiate riconoscere in seguito, ad esempio il dispositivo a cui appartiene. wg-easy assegna il successivo indirizzo libero del tunnel e genera la coppia di chiavi. Ogni riga del client contiene un codice QR e un file .conf scaricabile.
Installate l’app ufficiale WireGuard sul telefono, scegliete di aggiungere un tunnel tramite codice QR e inquadrate con la fotocamera il codice visualizzato sullo schermo. Il tunnel viene visualizzato con il nome immesso. Attivatelo: nella riga del client, l’interfaccia inizia a mostrare i contatori del traffico trasferito e l’orario dell’handshake più recente. Quando un telefono è connesso al tunnel, può raggiungere servizi che non avete mai pubblicato su Internet. In questo modo il telefono continua a caricare foto su un server fotografico self-hosted da qualsiasi luogo, senza che il server debba esporre una sola porta a Internet. Lo stesso approccio è utile per i contenuti multimediali: una libreria Jellyfin ricostruita come un videonoleggio degli anni 90 è piacevole da consultare dalla camera di un hotel, pur restando privata come quando il telefono era sulla LAN. Gli avvisi funzionano anche nella direzione opposta sullo stesso tunnel: un server ntfy self-hosted può inviare un messaggio al telefono nel momento in cui un processo di backup non riesce, senza dover mai rispondere a una richiesta proveniente da Internet.
Un client che non mostra alcun handshake dopo l’attivazione non sta raggiungendo il server. La causa riguarda UDP 51820, il firewall del provider oppure l’indirizzo dell’endpoint incorporato nella configurazione. Un client che mostra un handshake ma non consente di accedere a Internet indica invece un problema di forwarding o di DNS.
Su un computer desktop, scaricate il file .conf e importatelo nel client WireGuard, invece di riscriverne il contenuto. La chiave privata contenuta nel file viene generata una sola volta e viene mostrata una sola volta. Trattate il file come una chiave privata SSH.
Quando superare la UI
wg-easy è lo strumento giusto finché i peer sono persone e telefoni. La UI è più rapida della modifica manuale dei file di configurazione e la revoca di un telefono smarrito richiede un solo clic.
Raggiungerai i suoi limiti quando ti servirà qualcosa che la UI non gestisce. Il routing da sito a sito, in cui il AllowedIPs di un peer copre un'intera subnet remota invece di un singolo indirizzo, è solitamente il primo ostacolo. Seguono i tunnel split con regole di routing specifiche per peer o una configurazione generata dallo strumento di provisioning. A quel punto la configurazione manuale non è più difficile: è semplicemente diversa. La guida a WireGuard senza interfaccia mostra come creare lo stesso tunnel usando wg0.conf. Se preferisci smettere del tutto di eseguire il control plane, WireGuard a confronto con Tailscale descrive l'opzione gestita. L'equità di questo compromesso dipende da ciò che il coordination server può effettivamente raggiungere. Prima di affidargli la tua rete, conviene leggere Il modello di trust di Tailscale. Il costo è solitamente la domanda successiva. Cosa include effettivamente il piano gratuito di Tailscale spiega perché una famiglia o un piccolo team può usarlo senza pagare. Oltre quel limite, la fatturazione conta gli utenti invece dei dispositivi. È quindi una struttura di costi diversa da quella di un VPS che già paghi. Prima di migrare un team, verifica Quanto costa Tailscale dopo il superamento del piano gratuito. Il tunnel completo che hai appena creato ha un equivalente diretto anche in Tailscale. Pubblicare il VPS come exit node di Tailscale fornisce lo stesso percorso verso l'esterno attraverso il server. L'autorizzazione avviene nella console di amministrazione invece di essere scritta nella configurazione di ogni client. Anche il limite delle subnet ha un equivalente. Pubblicare un'intera rete privata dal VPS rende quella rete disponibile a ogni dispositivo del tailnet, senza dover modificare il AllowedIPs di ogni peer. Sono proprio queste modifiche a spingere oltre i limiti della UI. Se vuoi quella dashboard e il routing mesh automatico, ma non vuoi usare il coordination server di qualcun altro, Eseguire il proprio server NetBird su un VPS mantiene il control plane su hardware di tua proprietà. In cambio, devi configurare DNS e TLS, aspetti che wg-easy non richiede.
Se la parte poco familiare dell'esempio precedente era la sintassi di Compose e non WireGuard, Nozioni di base su Docker Compose su un VPS spiega il formato del file e i comandi di uso quotidiano.
FAQ
Perché wg-easy ignora WG_HOST e PASSWORD_HASH?
Queste variabili appartengono a wg-easy 14. La versione 15 è stata riscritta e il progetto upstream ha spostato quasi tutta la configurazione nel pannello di amministrazione dell'interfaccia web. Il container non legge nessuna delle due variabili: si avvia normalmente e, alla prima visita, chiede di creare un account di amministrazione. Impostare l'indirizzo host usato dai client nella pagina di configurazione iniziale.
È necessario SYS_MODULE se il kernel dispone già di WireGuard?
No. SYS_MODULE e il mount /lib/modules servono per consentire al container di caricare il modulo quando l'host non lo dispone. Su un host in cui sudo modprobe wireguard ha già esito positivo, questa capability non viene utilizzata. Rimuoverla è una misura di hardening ragionevole, mentre NET_ADMIN resta comunque necessario.
Il client si connette, ma non c'è accesso a Internet. Qual è il problema?
Un handshake senza traffico indica quasi sempre un problema di forwarding. Verificare che net.ipv4.ip_forward=1 e net.ipv4.conf.all.src_valid_mark=1 siano ancora presenti nel file compose, perché una copia modificata manualmente spesso li perde. Se il forwarding è attivo, controllare il server DNS ricevuto dal client. Un tunnel che invia tutto il traffico attraverso la VPN, ma usa un server DNS che non può più raggiungere, in un browser appare esattamente come una connessione non funzionante.
Come posso eseguire il backup dei client?
Tutto risiede nel volume denominato etc_wireguard, in un file wg0.json. Anche l'interfaccia dispone di un pulsante di backup che esporta gli stessi dati. Copiare il file in una posizione esterna al server prima di qualsiasi aggiornamento. Il ripristino consiste nel caricare il file durante la configurazione iniziale di un container nuovo.
Posso eseguire wg-easy dietro un reverse proxy?
Sì. Posizionare il proxy davanti a TCP 51821, terminare TLS sul proxy e impostare INSECURE=true sul container, in modo che accetti il collegamento HTTP in chiaro proveniente dal proxy. Lasciare UDP 51820 pubblicato direttamente, perché il traffico VPN usa UDP e non passa attraverso un proxy HTTP.