SSD Nodes Learn 8GB di RAM — $66/anno
Guide Matt ConnorDi Matt Connor · Aggiornato 2026-08-02

wg-easy: WireGuard con interfaccia web in Docker

Configura wg-easy con Docker Compose: porte, NET_ADMIN, sysctl, requisiti VPS e codici QR per collegare rapidamente i telefoni alla VPN.

Cosa stai creando

wg-easy è WireGuard con un'interfaccia web, eseguito come un unico container Docker. Gestisce l'interfaccia WireGuard e aggiunge un'interfaccia utente accessibile dal browser per creare client. Ogni client creato riceve un file di configurazione e un codice QR. Per collegare un telefono alla VPN, basta inquadrare lo schermo con la fotocamera.

Il tunnel è un'implementazione WireGuard standard. Il modulo del kernel trasferisce i pacchetti, quindi il throughput è uguale a quello di una configurazione scritta manualmente. Il vantaggio riguarda il ciclo di vita dei client: puoi aggiungere, disabilitare ed eliminare peer senza modificare un file di configurazione tramite SSH. Lo svantaggio è il minore controllo diretto su quel file, argomento trattato in la configurazione manuale di WireGuard su un VPS.

Ti 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, generalmente non può caricare il modulo WireGuard e il container non riuscirà 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. In quella versione, si impostavano WG_HOST sull'indirizzo del server e PASSWORD_HASH su un hash bcrypt della password amministrativa, entrambi come variabili d'ambiente. La versione 15 è una riscrittura. Le note ufficiali sulla migrazione dichiarano chiaramente che v15 non usa le stesse variabili d'ambiente di v14 e che la maggior parte delle 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 quindi chiede di creare un account amministrativo nel browser. Non è un bug. È il nuovo flusso di configurazione.

A luglio 2026, il major tag da fissare è 15. Fissare la versione principale invece di usare latest, perché un aggiornamento di versione principale modifica il formato della configurazione su disco e non consente un rollback corretto.

Il file compose

Crea una directory per lo stack e scrivici il file compose ufficiale. Questo è 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.yml

Il contenuto è simile al 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::/64

etc_wireguard è un volume denominato che contiene la chiave del server e tutti i client creati. Esegui il backup di questo volume; in caso contrario, una ricostruzione elimina tutti i peer. Se preferisci vedere questi file nel filesystem dell'host, sostituiscilo con un bind mount e leggi prima la differenza tra bind mount e volumi denominati, perché i permessi vengono gestiti in modo diverso.

Perché sono necessari NET_ADMIN, SYS_MODULE e i sysctl

Per impostazione predefinita, un container non può accedere allo stack di rete. Ciascuna 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 autorizzazione, il container si avvia e poi termina durante l'attivazione dell'interfaccia, perché ip link add wg0 type wireguard restituisce Operation not permitted.

SYS_MODULE, insieme al mount in sola lettura /lib/modules, consente al container di caricare il modulo del 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, l'handshake riesce e poi ogni pacchetto diretto verso Internet viene scartato. 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 più spesso. 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. È ciò che impedisce a un full tunnel di interrompere il proprio funzionamento.

Avviare il servizio e creare l'account amministratore

cd /etc/docker/containers/wg-easy
sudo docker compose up -d
sudo docker compose logs -f

Usare docker compose up e docker compose down, non start e stop. Il progetto upstream avverte che start, se eseguito 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. Alla prima visita viene visualizzata una pagina di configurazione, in cui è possibile 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'IP pubblico o il nome DNS del VPS. Se è errato, il codice QR fornito a un telefono punta a una destinazione irraggiungibile e l'handshake non viene mai completato.

Un'altra considerazione riguarda questa porta: wg-easy 15 rifiuta HTTP semplice a meno che non si imposti INSECURE=true. È possibile raggiungerlo tramite HTTPS con un certificato non attendibile oppure terminare TLS con 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 ogni interfaccia. È una pagina di accesso a 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, valutata prima di ufw; per questo una regola di diniego di ufw non la chiude. È importante comprendere questo comportamento, e perché le porte pubblicate da Docker ignorano ufw lo spiega in dettaglio.

La soluzione semplice consiste nel associare l'interfaccia web all'interfaccia loopback e raggiungerla tramite un tunnel SSH:

    ports:
      - "51820:51820/udp"
      - "127.0.0.1:51821:51821/tcp"
    environment:
      - INSECURE=true

Poi, dal laptop:

ssh -L 51821:127.0.0.1:51821 youruser@your.server.address

Apri http://127.0.0.1:51821 nel browser del laptop. Il traffico è cifrato da SSH, nessun altro sistema può rispondere su quella porta e INSECURE=true è sicuro in questo caso perché il collegamento HTTP non cifrato non lascia mai l'interfaccia loopback.

Aprire UDP 51820 e controllare entrambi i firewall

WireGuard richiede che UDP 51820 sia raggiungibile da Internet. 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 entrambe le posizioni. Se gestisci il firewall dell'host con ufw, le regole ufw di base per un VPS sono più rapide rispetto alla scrittura manuale delle regole nftables.

Verifica che il container sia effettivamente in ascolto:

sudo ss -ulnp | grep 51820

Dovresti vedere un socket UDP in ascolto. Se quella riga non restituisce alcun risultato, il container non ha mai attivato l'interfaccia e sudo docker compose logs wg-easy indicherà il motivo.

Creare un client e scansionarlo su un telefono

Nell'interfaccia, crea un client e assegnagli un nome che riconoscerai in seguito, ad esempio il dispositivo a cui appartiene. wg-easy assegna il successivo indirizzo tunnel disponibile e genera la coppia di chiavi. Ogni riga del client include un codice QR e un file .conf scaricabile.

Installa l'app ufficiale WireGuard sul telefono, scegli di aggiungere un tunnel da un codice QR e inquadra con la fotocamera il codice visualizzato sullo schermo. Il tunnel viene visualizzato con il nome inserito. Attivalo: la riga del client nell'interfaccia inizierà a mostrare i contatori del traffico e l'ora dell'handshake più recente.

Se un client non mostra alcun handshake dopo l'attivazione, non sta raggiungendo affatto il server. Il problema riguarda UDP 51820, nel firewall del provider oppure nell'indirizzo dell'endpoint incorporato nella configurazione. Se un client mostra un handshake ma non consente l'accesso a Internet, il problema riguarda l'inoltro del traffico oppure il DNS.

Su un desktop, scarica il file .conf e importalo nel client WireGuard invece di reinserirne manualmente il contenuto. La chiave privata contenuta nel file viene generata una sola volta e visualizzata una sola volta. Proteggi il file come proteggeresti una chiave privata SSH.

Quando superare l'interfaccia

wg-easy è lo strumento adatto finché i peer sono persone e telefoni. L'interfaccia è più rapida della modifica dei file di configurazione e la revoca dell'accesso a un telefono smarrito richiede un solo clic.

I suoi limiti emergono quando serve qualcosa che l'interfaccia non può rappresentare. Il routing da sito a sito, in cui il AllowedIPs di un peer copre un'intera subnet remota invece di un singolo indirizzo, è in genere il primo ostacolo. Seguono i tunnel split con regole di routing per peer oppure una configurazione generata dallo strumento di provisioning. A quel punto la configurazione scritta manualmente non è più difficile: è semplicemente diversa. La guida WireGuard senza strumenti aggiuntivi mostra lo stesso tunnel creato con wg0.conf. Se invece si preferisce smettere del tutto di eseguire il control plane, WireGuard a confronto con Tailscale illustra l'opzione gestita.

Se la sintassi di compose precedente era la parte non familiare, 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, quindi si avvia normalmente e, alla prima visita, chiede di creare un account amministratore. Imposta l'indirizzo host visibile ai client nella pagina di configurazione.

SYS_MODULE è necessario 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. In un host in cui sudo modprobe wireguard ha già esito positivo, questa capability non viene utilizzata. Rimuoverla è una misura di hardening ragionevole; 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. Verifica 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 può perderli. Se il forwarding è attivo, controlla il server DNS ricevuto dal client. Un tunnel che invia tutto il traffico attraverso la VPN, ma utilizza un server DNS che non può più raggiungere, appare in un browser esattamente come una connessione non funzionante.

Come posso eseguire il backup dei client?

Tutto si trova nel volume denominato etc_wireguard, in un file wg0.json. L'interfaccia dispone anche di un pulsante di backup che esporta gli stessi dati. Copia il file in una posizione esterna al server prima di qualsiasi upgrade. Il ripristino consiste nel caricare il file durante la procedura di configurazione di un container nuovo.

Posso eseguire wg-easy dietro un reverse proxy?

Sì. Posiziona il proxy davanti a TCP 51821, termina TLS sul proxy e imposta INSECURE=true sul container, in modo che accetti il passaggio HTTP non cifrato proveniente dal proxy. Mantieni UDP 51820 pubblicato direttamente, perché il traffico VPN utilizza UDP e non passa attraverso un proxy HTTP.