Come configurare WireGuard su un VPS Linux
Guida pratica a WireGuard su VPS Linux: chiavi, wg0.conf, forwarding, NAT, AllowedIPs, DNS e diagnosi degli handshake falliti. Compatibile con kernel Linux 5.6+.
Cosa stai configurando
Una VPN WireGuard su un server di tua proprietà richiede circa quaranta righe di configurazione: una coppia di chiavi, un file di interfaccia, un parametro sysctl, una regola NAT e un'apertura nel firewall. L'installazione è semplice. Per questo, gran parte di questa guida tratta i problemi più comuni: permessi delle chiavi, AllowedIPs, forwarding e DNS.
WireGuard è un tunnel Layer 3 implementato nel kernel ed è incluso nel kernel Linux principale dalla versione 5.6. Ubuntu 24.04 e Debian 13 lo forniscono quindi senza moduli esterni. Non esistono negoziazione dei cifrari, autorità di certificazione o passaggi con nome utente e password: un peer è una chiave pubblica associata agli indirizzi IP che può utilizzare. Un pacchetto che non supera il controllo MAC viene scartato senza risposta, quindi la porta non risponde alle scansioni. Lo svantaggio è che non esiste un server di autenticazione: per revocare l'accesso è necessario eliminare il peer dal server.
Verifica innanzitutto la virtualizzazione
WireGuard richiede un kernel in cui sia possibile caricare un modulo. Su un VPS KVM funziona senza configurazioni aggiuntive. Nella virtualizzazione basata su container, che condivide il kernel dell’host, ad esempio OpenVZ o LXC, il primo comando restituisce RTNETLINK answers: Operation not supported. In questo caso, l’alternativa è l’implementazione userspace wireguard-go. Verifica prima con sudo modprobe wireguard && echo ok.
Generare le chiavi senza renderle accessibili
Un /etc/wireguard/server.key leggibile da chiunque equivale a non avere alcuna VPN. La direttiva umask 077 && wg genkey | sudo tee ... comunemente utilizzata non è affidabile, perché sudo applica una propria umask al file creato da tee. Impostate esplicitamente i permessi.
sudo apt update && sudo apt install -y wireguard nftables
sudo install -d -m 700 /etc/wireguard
sudo sh -c 'umask 077; wg genkey > /etc/wireguard/server.key'
sudo sh -c 'wg pubkey < /etc/wireguard/server.key > /etc/wireguard/server.pub'
sudo chmod 600 /etc/wireguard/server.keyGenerate la coppia del client nello stesso modo. wg genpsk aggiunge una chiave precondivisa facoltativa, con una riga in ciascuna configurazione.
Interfaccia del server: /etc/wireguard/wg0.conf
[Interface]
Address = 10.8.0.1/24
ListenPort = 51820
PrivateKey = <contents of /etc/wireguard/server.key>
[Peer]
PublicKey = <laptop public key>
PresharedKey = <psk, optional>
AllowedIPs = 10.8.0.2/32chmod 600; un avviso all'avvio che segnala che il file è accessibile a tutti indica che questo passaggio è stato omesso. Address è l'indirizzo del server all'interno del tunnel e include la maschera dell'intera subnet VPN. Scegliere un intervallo che non venga utilizzato normalmente. 192.168.1.0/24 entra in conflitto con metà dei router domestici dietro cui si trovano i client e il tunnel perde quindi silenziosamente rispetto alla route locale.
Sul server, il AllowedIPs di un peer è un /32, cioè l'unico indirizzo del tunnel assegnato a quel client. Se si assegna lo stesso allowed IP a due peer, l'indirizzo passa al peer configurato per ultimo e il primo smette di ricevere traffico senza che venga stampato alcun errore. Lasciare SaveConfig non impostato, altrimenti wg-quick down riscrive questo file in base allo stato attivo.
Trasforma il server in un router
Un server Linux scarta i pacchetti che non sono indirizzati a sé. Per impostazione predefinita mancano sia l'inoltro dei pacchetti sia il NAT di origine.
printf 'net.ipv4.ip_forward = 1\nnet.ipv6.conf.all.forwarding = 1\n' \
| sudo tee /etc/sysctl.d/99-wireguard.conf
sudo sysctl --system
sysctl net.ipv4.ip_forwardUn comando sysctl -w isolato funziona fino al riavvio successivo, poi smette di funzionare senza messaggi. Il NAT richiede l'interfaccia di egress, cioè la scheda di rete che raggiunge Internet, non wg0. Non dare per scontato eth0; ricava il nome da ip route show default, perché le immagini attuali usano nomi come enp1s0 o ens3.
Firewall: la porta e il percorso di inoltro
Un unico file nftables gestisce filtro e NAT. Scrivi /etc/nftables.conf: svuota il ruleset esistente, quindi non eseguire questa operazione su un server già gestito da ufw o Docker.
#!/usr/sbin/nft -f
flush ruleset
table inet filter {
chain input {
type filter hook input priority filter; policy drop;
ct state established,related accept
iif lo accept
tcp dport 22 accept
udp dport 51820 accept
}
chain forward {
type filter hook forward priority filter; policy drop;
ct state established,related accept
iifname "wg0" oifname "enp1s0" accept
}
}
table ip nat {
chain postrouting {
type nat hook postrouting priority srcnat; policy accept;
ip saddr 10.8.0.0/24 oifname "enp1s0" masquerade
}
}Applicalo con sudo systemctl enable --now nftables e mantieni aperta una seconda sessione SSH: policy drop, insieme a un refuso nella regola SSH, può impedirti di accedere al tuo server. Nota cosa la catena forward non consente, wg0 a wg0. I peer possono raggiungere Internet, ma non tra loro; aggiungi iifname "wg0" oifname "wg0" accept per una VPN peer-to-peer. La stessa catena controlla anche ciò che un peer può raggiungere direttamente sul server. Questo è importante quando il server funge anche da server di sviluppo remoto che esegue Claude Code in tmux e preferisci non esporre pubblicamente quella parte del sistema.
Su un server con ufw: ufw allow 51820/udp, DEFAULT_FORWARD_POLICY="ACCEPT" in /etc/default/ufw e una regola POSTROUTING MASQUERADE *nat all'inizio di /etc/ufw/before.rules.
Avvialo tramite systemd
sudo systemctl enable --now wg-quick@wg0
sudo wg showwg-quick crea l'interfaccia, aggiunge gli indirizzi e installa le route derivate da AllowedIPs. enable --now è la parte importante: un wg-quick up wg0 eseguito manualmente viene perso al riavvio successivo e gli aggiornamenti del kernel richiedono riavvii. Un'unità che non torna attiva dopo uno di questi riavvii rimane silenziosa finché qualcuno non prova a connettersi. Per questo, un drop-in OnFailure= su wg-quick@wg0, configurato per usare il tuo server ntfy, è il modo più economico per ricevere la notifica sul telefono invece di scoprirlo tramite un utente che non riesce ad accedere.
La configurazione del client e l'impostazione che quasi tutti interpretano in modo errato
[Interface]
PrivateKey = <laptop private key>
Address = 10.8.0.2/32
DNS = 10.8.0.1
[Peer]
PublicKey = <server public key>
Endpoint = vpn.example.com:51820
AllowedIPs = 0.0.0.0/0, ::/0
PersistentKeepalive = 25AllowedIPs svolge contemporaneamente due funzioni distinte. Confonderle è la causa della maggior parte dei dubbi su WireGuard.
In uscita è una tabella di routing. Un pacchetto la cui destinazione corrisponde a AllowedIPs di un peer viene cifrato e inviato a quel peer. 0.0.0.0/0, ::/0 invia tutto attraverso il tunnel: è un full tunnel, con il server come route predefinita. Uno split tunnel contiene un elenco più ristretto: AllowedIPs = 10.8.0.0/24, 10.20.0.0/16 instrada il traffico VPN e una rete privata dietro il server, mentre tutto il resto continua a usare la route locale. Questo elenco ristretto consente di mantenere i servizi completamente non esposti a Internet: una istanza privata di Nextcloud su un VPS associata all'indirizzo del tunnel oppure le VM del laboratorio di virtualizzazione annidata in esecuzione sullo stesso host restano raggiungibili dai peer e invisibili a tutti gli altri.
In ingresso è un elenco di controllo degli accessi. Un pacchetto decrittografato proveniente da un peer il cui indirizzo sorgente non è incluso in AllowedIPs di quel peer viene eliminato. Per questo il server indica 10.8.0.2/32 per il laptop: inserire 0.0.0.0/0 in quel punto consentirebbe al client di falsificare qualsiasi indirizzo del tunnel.
PersistentKeepalive serve ai client che si trovano dietro NAT, dove il router mantiene aperta la mappatura UDP soltanto finché transitano pacchetti. Quando la mappatura scade, il server non può più raggiungere il client. PersistentKeepalive = 25 mantiene aperta la mappatura; impostalo sul client, non su un server con un indirizzo IP pubblico.
DNS e la fuga che nessuno nota
Con AllowedIPs = 0.0.0.0/0 e senza la riga DNS =, il client mantiene il resolver appreso dalla rete locale, cioè dal router del bar all’indirizzo 192.168.1.1. Questa route è più specifica della route predefinita, quindi le query DNS escono in chiaro tramite il collegamento locale, mentre tutto il resto passa nel tunnel. Il traffico è privato; l’elenco dei nomi consultati no.
Esistono due opzioni corrette. Indica DNS come resolver pubblico (DNS = 9.9.9.9): le query passeranno nel tunnel e usciranno dal server, anche se il resolver continuerà a poterle vedere. In alternativa, esegui unbound o dnsmasq in ascolto su 10.8.0.1, imposta DNS = 10.8.0.1 e aggiungi udp dport 53 iifname "wg0" accept alla catena input; imposta quella riga e dimentica il resolver: in caso contrario, non verrà risolto alcun nome.
Nei client Linux, wg-quick applica DNS tramite resolvconf; se è assente, ottieni resolvconf: command not found. Installa openresolv oppure imposta PostUp = resolvectl dns %i 10.8.0.1 su un client con systemd-resolved.
Aggiungere e rimuovere peer senza interrompere il tunnel
Riavviare l’interfaccia per aggiungere un utente disconnette tutti i client collegati. Aggiungere il blocco [Peer] a wg0.conf, quindi ricaricare in sede l’insieme dei peer.
sudo bash -c 'wg syncconf wg0 <(wg-quick strip wg0)'wg-quick strip stampa la configurazione senza le chiavi utilizzate solo da wg-quick (Address, DNS, PostUp), mentre syncconf applica le differenze senza interrompere le sessioni attive. Aggiorna solo i peer: se cambia Address, è comunque necessario eseguire un arresto e un riavvio completi. Revocare con sudo wg set wg0 peer <public key> remove, quindi eliminare il blocco dal file; in caso contrario, verrà ripristinato al successivo reload.
Modalità di errore e stringhe visualizzate
L'handshake non viene mai completato. wg show elenca il peer senza latest handshake e il client registra:
Handshake for peer 1 (10.0.0.10:51820) did not complete after 5 seconds, retrying (try 2)Non arrivano pacchetti oppure i pacchetti non vengono accettati. Verificare nell'ordine: che UDP 51820 sia aperto nel firewall del VPS e nel firewall di rete del provider, che nella maggior parte dei pannelli è un controllo separato; che l'indirizzo e la porta Endpoint siano corretti; che le chiavi non siano state scambiate. La chiave nel blocco [Peer] del client deve essere la chiave pubblica del server e viceversa. Incollare una chiave privata o la chiave pubblica del client produce esattamente questo sintomo. sudo tcpdump -ni any udp port 51820 sul server mostra se arrivano pacchetti. Il modulo del kernel non registra nulla per impostazione predefinita. I messaggi di WireGuard compaiono in dmesg solo dopo l'abilitazione del debug dinamico (echo module wireguard +p | sudo tee /sys/kernel/debug/dynamic_debug/control). Con il debug attivo, una chiave non corrispondente viene indicata come pacchetto scartato per MAC non valido.
L'handshake funziona, ma Internet no. ping 10.8.0.1 ha esito positivo, ma ping 1.1.1.1 va in timeout: mancano il forwarding o il NAT. Verificare che sysctl net.ipv4.ip_forward restituisca 1, quindi osservare i contatori mentre il client esegue il ping, usando sudo nft list ruleset o sudo iptables -t nat -L POSTROUTING -n -v. Nessun pacchetto sulla regola di masquerade indica che il nome dell'interfaccia di uscita è errato. Un contatore in aumento senza risposte indica un problema nella policy della catena di forwarding.
Internet funziona, ma i nomi no. ping 1.1.1.1 ha esito positivo e curl https://example.com restituisce Could not resolve host. Manca la riga DNS oppure indica un resolver non raggiungibile dall'interno del tunnel.
Alcuni siti HTTPS restano in attesa. SSH e ping funzionano, ma le pagine di grandi dimensioni si bloccano. Si tratta del path MTU: il tunnel aggiunge overhead e alcuni collegamenti intermedi scartano i pacchetti troppo grandi senza che un messaggio ICMP riesca a tornare indietro. Ridurre MTU nel client [Interface], provare 1420, quindi 1380 e infine 1280. Se ridurre l'MTU elimina i blocchi ma il throughput resta insoddisfacente, non procedere per valori arrotondati scelti a caso. Seguire la procedura per trovare il vero path MTU con ricerca per bisezione e clamping del TCP MSS, che consente anche di escludere le cause non legate al tunnel.
L'interfaccia rifiuta di avviarsi. Address already in use indica che un altro processo sta usando UDP 51820. Cannot find device wg0 dopo un up non riuscito indica in genere che la configurazione è stata rifiutata. Leggere journalctl -u wg-quick@wg0 -n 50.
Migrazione da Streisand o OpenVPN
Streisand non è più mantenuto e il relativo repository è stato archiviato. Eseguire una VPN su un sistema di automazione abbandonato introduce un problema di sicurezza che si aggrava nel tempo. Non è disponibile un aggiornamento in-place e la PKI di OpenVPN non può essere convertita: WireGuard non usa certificati, CA o scadenze. Ogni client deve quindi ricevere una nuova coppia di chiavi.
Eseguire la migrazione in parallelo. WireGuard sulla porta UDP 51820 può coesistere con OpenVPN sulla porta 1194 sullo stesso server. Configurare wg0, spostare i client uno alla volta, quindi arrestare il servizio precedente. Il modello di autenticazione con nome utente e password e quello di revoca di OpenVPN non vengono trasferiti. Se servono account o una traccia di audit, aggiungere questi componenti sopra WireGuard.
Backup, aggiornamenti e aspetti critici su larga scala
/etc/wireguard è il server. Eseguine il backup (sudo tar czf wg-backup.tgz -C /etc wireguard, modalità 600, conservato fuori dal server) per poterlo ricreare su un VPS nuovo in pochi minuti. Se perdi la chiave privata del server, devi rigenerare la configurazione di ogni client, perché i client fissano la chiave pubblica del server. Gli aggiornamenti consistono in un normale apt upgrade seguito da un riavvio per gli aggiornamenti del kernel, mentre wg-quick@wg0 si riattiva automaticamente se lo hai abilitato.
Lo stato di ogni peer occupa poco spazio e la crittografia viene eseguita nel kernel. Il limite dipende quindi dalla CPU e dalla larghezza di banda disponibili sul VPS, non da questo file di configurazione. Misuralo con iperf3 attraverso il tunnel invece di affidarti a un valore pubblicato. Su larga scala, il problema è operativo. Ogni peer deve avere un indirizzo IP del tunnel univoco. Modificare manualmente sessanta blocchi [Peer] è il modo migliore per introdurre AllowedIPs duplicati: genera le configurazioni con uno script. Un server corrisponde a un endpoint UDP e a un singolo punto di errore. WireGuard non supporta il clustering: la ridondanza richiede un secondo server con chiavi proprie. La rotazione delle chiavi resta manuale. Devi quindi registrare chi possiede ogni chiave e come revocarla. Quando questa gestione supera le dimensioni di un file di testo, la soluzione abituale è usare un control plane sopra lo stesso data plane del kernel. Un server NetBird self-hosted gestisce l'allocazione degli indirizzi, la distribuzione dei peer e le setup key che altrimenti dovresti configurare manualmente. Se gestire personalmente questo control plane aggiunge un server di troppo, Tailscale ne ospita uno per te. Il piano gratuito copre sei utenti con dispositivi illimitati, una soglia sufficiente perché la maggior parte delle infrastrutture personali non debba pagare. Oltre questa soglia, il costo dipende dalle persone e non dalle macchine. Quanto paga effettivamente una famiglia o un piccolo team dipende dal numero di persone con credenziali di accesso, non dal numero di peer che avresti dovuto inserire manualmente in wg0.conf. In questo modello, la gestione split-tunnel di AllowedIPs diventa la pubblicazione delle reti private tramite un subnet router, annunciata una sola volta da un VPS e approvata centralmente invece di essere incollata in ogni file client. La convenienza di questo compromesso dipende da ciò che il control plane ospitato può effettivamente raggiungere. Non conserva mai le chiavi che cifrano il traffico, anche se decide quali peer possono conoscere gli altri.
Tutto questo richiede un server Linux sotto il tuo controllo, un IP pubblico, un kernel in cui sia possibile caricare un modulo e un firewall che gestisci integralmente.
FAQ
Perché l'handshake WireGuard non viene mai completato?
wg show che mostra un peer senza latest handshake indica che i pacchetti non arrivano oppure non vengono accettati. Controlla UDP 51820 nel firewall del VPS e nel firewall di rete separato del provider. Verifica il nome host e la porta Endpoint. Controlla quindi che le chiavi non siano invertite: il blocco [Peer] del client deve contenere la chiave pubblica del server. sudo tcpdump -ni any udp port 51820 sul server mostra se i pacchetti arrivano. dmesg segnala soltanto gli errori di handshake di WireGuard dopo l'abilitazione del debug dinamico (echo module wireguard +p | sudo tee /sys/kernel/debug/dynamic_debug/control). In questo caso, una mancata corrispondenza delle chiavi appare come uno scarto per MAC non valido.
Il tunnel si connette, ma non ho accesso a Internet. Che cosa manca?
Se ping 10.8.0.1 funziona mentre ping 1.1.1.1 va in timeout, il problema riguarda l'inoltro o il NAT. Verifica che sysctl net.ipv4.ip_forward restituisca 1 e che sia impostato in /etc/sysctl.d/, non soltanto tramite un sysctl -w che viene perso al riavvio. Controlla quindi che la regola di masquerade utilizzi l'interfaccia di uscita effettiva, indicata da ip route show default, enp1s0 o ens3, raramente eth0.
La riga DNS = è necessaria nella configurazione del client?
Con un tunnel completo e senza la riga DNS =, il client mantiene il resolver appreso dalla rete locale. Le relative richieste passano in chiaro sul collegamento locale, mentre tutto il resto viene instradato nel tunnel. Imposta DNS su un resolver pubblico oppure esegui unbound/dnsmasq in ascolto su 10.8.0.1 e apri udp dport 53 iifname "wg0" nella catena di input.
Che cosa controlla esattamente AllowedIPs?
Svolge due funzioni. In uscita è una tabella di routing: il traffico che corrisponde a AllowedIPs di un peer viene cifrato e inviato a quel peer. In ingresso è un elenco di controllo degli accessi: un pacchetto decifrato con un indirizzo sorgente esterno a AllowedIPs del peer viene scartato. Per questo, sul server viene indicato un /32 per ogni client, mentre sul client può essere indicato 0.0.0.0/0.
WireGuard funziona su qualsiasi VPS?
Su un VPS KVM funziona con il modulo del kernel, senza configurazioni aggiuntive. Nella virtualizzazione basata su container che condivide il kernel dell'host, ad esempio OpenVZ o LXC, modprobe wireguard non riesce con Operation not supported e l'alternativa è l'implementazione userspace wireguard-go. Esegui sudo modprobe wireguard && echo ok prima di qualsiasi altra operazione.