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 esporle
Un /etc/wireguard/server.key leggibile da tutti equivale a non avere alcuna VPN. La riga umask 077 && wg genkey | sudo tee ... comune funziona: a meno che sudoers non imposti umask_override, disattivato per impostazione predefinita, sudo combina il tuo umask con il proprio valore predefinito 0022 e non lo riduce mai; di conseguenza, il file creato da tee ha modalità 600. Le chiavi vengono esposte quando si esegue quella pipe senza umask 077, con un umask normale 022 oppure su un sistema in cui sudoers abilita umask_override. Imposta esplicitamente la modalità e il problema non si presenta più.
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.keyGenera 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 file nftables contiene sia le regole di filtraggio sia quelle NAT. Scrivi /etc/nftables.conf: svuota il ruleset esistente, quindi non eseguire questo comando 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 errore di battitura 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 possono comunicare tra loro; aggiungi iifname "wg0" oifname "wg0" accept per una VPN peer-to-peer. L'accesso di un peer al server stesso non è gestito da questa catena: i pacchetti indirizzati al server passano dall'hook input, non da forward, quindi decide la catena input. Nella configurazione mostrata, questa consente a un peer di raggiungere SSH e nient'altro, nemmeno di eseguire il ping di 10.8.0.1. Aggiungi iifname "wg0" icmp type echo-request accept in quella catena, oltre a una regola iifname "wg0" per ogni servizio che vuoi rendere raggiungibile soltanto attraverso il tunnel. Questo è importante quando il server funge anche da ambiente di sviluppo remoto con Claude Code in tmux e preferisci non esporre pubblicamente quella parte.
Su un server 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 i log del client mostrano:
Handshake for peer 1 (10.0.0.10:51820) did not complete after 5 seconds, retrying (try 2)Non arriva nulla oppure nulla viene accettato. Verificare, nell'ordine: che UDP 51820 sia aperta 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 avere abilitato il debug dinamico (echo module wireguard +p | sudo tee /sys/kernel/debug/dynamic_debug/control). Con il debug attivo, una mancata corrispondenza delle chiavi appare come un pacchetto eliminato 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: manca l'inoltro o il NAT. Verificare che sysctl net.ipv4.ip_forward restituisca 1, quindi monitorare i contatori mentre il client esegue il ping, usando sudo nft list ruleset o sudo iptables -t nat -L POSTROUTING -n -v. Zero pacchetti sulla regola di masquerade indica che il nome dell'interfaccia di uscita è errato; un contatore in aumento senza risposte indica la policy della catena forward.
Internet funziona, ma i nomi no. ping 1.1.1.1 ha esito positivo e curl https://example.com restituisce Could not resolve host. La riga DNS manca oppure indica un resolver non raggiungibile dall'interno del tunnel.
Alcuni siti HTTPS rimangono bloccati. SSH e il ping funzionano, ma le pagine di grandi dimensioni si interrompono. Il problema è il path MTU: il tunnel aggiunge overhead e un collegamento intermedio può eliminare i pacchetti troppo grandi senza restituire un messaggio ICMP. Impostare MTU nel blocco client [Interface]. Su un collegamento ordinario da 1500 byte wg-quick seleziona già 1420 (1500 meno gli 80 byte di overhead), quindi impostare 1420 non cambia nulla: provare 1380 e poi 1280. Se ridurre l'MTU elimina i blocchi ma il throughput resta insoddisfacente, non procedere per tentativi con valori arrotondati. Determinare il path MTU effettivo tramite ricerca per bisezione e limitazione del TCP MSS, escludendo anche le cause che non dipendono dal tunnel.
L'interfaccia non si avvia. Address already in use indica che un altro processo utilizza 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. Esegui il backup (sudo tar czf wg-backup.tgz -C /etc wireguard, modalità 600, conservato fuori dal server) e potrai ricostruirlo in pochi minuti su un VPS nuovo. Se perdi la chiave privata del server, devi emettere nuovamente 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; wg-quick@wg0 viene nuovamente attivato automaticamente se lo hai abilitato.
Lo stato di ogni peer occupa poco spazio e la crittografia viene eseguita nel kernel, quindi il limite dipende da CPU e 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 richiede un indirizzo IP del tunnel univoco. Modificare manualmente sessanta blocchi [Peer] è il modo migliore per introdurre duplicati AllowedIPs: genera le configurazioni con uno script. Un solo server significa un solo endpoint UDP e un solo punto di errore, e WireGuard non offre il clustering. La ridondanza richiede un secondo server con chiavi proprie. La rotazione delle chiavi resta manuale, quindi annota chi conserva ogni chiave e come revocarla. Quando questa gestione supera ciò che è pratico in un file di testo, la soluzione usuale è aggiungere un control plane allo stesso data plane del kernel. Un server NetBird self-hosted gestisce l'assegnazione degli indirizzi, la distribuzione dei peer e le setup key che altrimenti dovresti gestire manualmente. Se eseguire autonomamente questo control plane significa aggiungere un server di troppo, Tailscale ne ospita uno per te. Il piano gratuito Personal copre sei utenti con dispositivi utente illimitati e, inizialmente, fino a 50 risorse con tag, come i server, una capacità sufficiente perché la maggior parte delle infrastrutture personali non debba pagare il servizio. Oltre questo limite, il costo dipende dalle persone e non dalle macchine, quindi quanto paga effettivamente una famiglia o un piccolo team dipende dal numero di utenti con credenziali, non dal numero di peer che avresti dovuto inserire manualmente in wg0.conf. In questo modello, la gestione AllowedIPs dello split tunnel diventa pubblicare i tuoi intervalli privati da un subnet router, annunciati una sola volta da un VPS e approvati centralmente invece di essere copiati in ogni file client. La convenienza di questa soluzione dipende da ciò che un control plane ospitato può effettivamente raggiungere. Non conserva mai le chiavi che cifrano il traffico, anche se decide quali peer possono conoscere l'esistenza degli 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 direttamente dall'inizio alla fine.
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.