Utilizzi una VPN self-hosted. Ti chiedi cosa succede se il tunnel si interrompe per 12 secondi durante il caricamento di qualcosa di sensibile. Nella maggior parte delle configurazioni Linux predefinite: il traffico scorre liberamente attraverso il tuo percorso predefinito, senza alcuna indicazione visibile. Perdite DNS locali, IP reale che appare nei log del server remoto. Un kill switch a livello di applicazione non ti salverà — questo deve essere bloccato a livello di kernel.
Questa guida imposta un kill switch iptables + systemd che garantisce che nulla esca dalla tua macchina in chiaro, anche durante il gap di 50 ms tra due handshake di WireGuard.
Perché un kill switch netfilter è l'unica vera garanzia
L'industria delle VPN commerciali parla di "kill switch" da sette anni, ma la maggior parte delle implementazioni è fragile perché operano a livello di applicazione: l'app di NordVPN o ExpressVPN rileva una caduta del tunnel e interrompe il traffico manipolando la tabella di routing. Il problema: durante i 200-800 millisecondi necessari all'applicazione per rilevare la caduta e reagire, i pacchetti escono in chiaro attraverso l'interfaccia predefinita. È sufficiente per una connessione BitTorrent attiva per esporre il tuo IP reale al tracker, o per un upload multipart in corso su S3 per rivelare la tua origine geografica.
Il kill switch netfilter (iptables o nftables) evita questo problema posizionando la regola di filtraggio nel kernel Linux stesso, non in un processo user-space. I pacchetti attraversano il sottosistema netfilter ad ogni syscall send/sendmsg/sendto — non c'è finestra di latenza applicativa. Se WireGuard si arresta, i pacchetti continuano ad essere DROP-pati da netfilter fino al ritorno del tunnel, indipendentemente dallo stato del processo WireGuard. Ecco perché Mullvad raccomanda netfilter come secondo strato oltre alla loro app, e perché le guide del Tor Project menzionano esplicitamente il kill switch netfilter per gli host che eseguono relè.
La nostra scelta di iptables piuttosto che nftables si riduce al supporto: Ubuntu Server 22.04 LTS e Debian 12 hanno ancora iptables come strumento de facto anche se il backend è nftables sotto il cofano. I comandi iptables sono copiabili-incollabili da qualsiasi documento degli ultimi 10 anni, il che riduce il rischio di errori di battitura e facilita la manutenzione. Se stai iniziando da zero nel 2026 su un nuovo host e conosci nftables, vai direttamente con nftables — la sintassi è più pulita e l'ordine delle catene è esplicito.
Il modello che costruiamo
L'idea centrale in 3 frasi:
- Tutto il traffico OUTPUT è DROP di default a meno che non esca tramite l'interfaccia VPN (
wg0). - Eccezione: la connessione UDP al server VPN (handshake) rimane consentita — senza di essa, il tunnel non può ristabilirsi dopo una caduta.
- systemd garantisce che queste regole iptables si applichino prima di WireGuard e sopravvivano ai riavvii del tunnel.
Risultato: se WireGuard si arresta, la tua macchina non può letteralmente inviare nulla tranne l'handshake VPN. Vedi "nessuna connessione" nel browser, ma nessun pacchetto trapela.
Preparazione
Su Ubuntu/Debian (testato su Ubuntu 22.04 e 24.04, kernel 5.15+):
sudo apt update
sudo apt install -y iptables iptables-persistent
# Al prompt di netfilter-persistent: SÌ per IPv4 e IPv6
Nota questi elementi prima di continuare:
- L'IP pubblico della VPN:
nslookup vpn.example.como controlla la lineaEndpointinwg0.conf. - La porta UDP della VPN: solitamente
51820per WireGuard. - L'interfaccia VPN:
wg0di default, può esserewg1con più tunnel. - La subnet LAN locale da mantenere raggiungibile (192.168.1.0/24, 10.0.0.0/8, ecc.) — così stampante e NAS non si interrompono.
Regole iptables IPv4
Crea /etc/iptables/rules.v4 con questo contenuto (adatta i valori tra parentesi):
*filter
:INPUT DROP [0:0]
:FORWARD DROP [0:0]
:OUTPUT DROP [0:0]
# Loopback sempre consentito
-A INPUT -i lo -j ACCEPT
-A OUTPUT -o lo -j ACCEPT
# Connessioni già stabilite (risposte handshake VPN, ecc.)
-A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
-A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
# SSH in entrata (se amministri da remoto — altrimenti elimina queste 2 righe)
-A INPUT -p tcp --dport 22 -m state --state NEW -j ACCEPT
# Handshake VPN in uscita: solo traffico fuori dal tunnel consentito
-A OUTPUT -p udp -d 1.2.3.4 --dport 51820 -j ACCEPT
# DNS LAN locale (Pi-hole, router) — opzionale
-A OUTPUT -d 192.168.1.0/24 -j ACCEPT
# Tutto il resto OUTPUT va tramite interfaccia VPN
-A OUTPUT -o wg0 -j ACCEPT
# Consenti in entrata su wg0
-A INPUT -i wg0 -j ACCEPT
COMMIT
Sostituisci:
1.2.3.4con l'effettivo IP pubblico del tuo server VPN51820con la tua porta WireGuard192.168.1.0/24con la tua subnet LAN- Elimina la linea
--dport 22se non hai bisogno di amministrazione SSH in entrata
Carica le regole:
sudo iptables-restore < /etc/iptables/rules.v4
sudo iptables -L -v -n
# Conferma che i contatori della catena DROP siano a 0
Regole ip6tables (CRITICO)
Senza IPv6 esplicitamente bloccato, molte distribuzioni lo abilitano di default e il traffico trapela. Crea /etc/iptables/rules.v6:
*filter
:INPUT DROP [0:0]
:FORWARD DROP [0:0]
:OUTPUT DROP [0:0]
-A INPUT -i lo -j ACCEPT
-A OUTPUT -o lo -j ACCEPT
-A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
-A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
# Se la tua VPN supporta IPv6 (raro in self-host):
# -A OUTPUT -p udp -d 2001:db8::1 --dport 51820 -j ACCEPT
# -A OUTPUT -o wg0 -j ACCEPT
# -A INPUT -i wg0 -j ACCEPT
# Altrimenti tutto IPv6 fuori dal loopback è DROP
COMMIT
Carica:
sudo ip6tables-restore < /etc/iptables/rules.v6
A questo punto puoi testare: senza il tunnel WireGuard, nessun ping verso Internet passa. Previsto.
Ordinamento del servizio systemd per WireGuard dopo iptables
Problema predefinito: wg-quick@wg0 potrebbe avviarsi prima di netfilter-persistent. Durante quella finestra (~1-3 secondi), il tunnel è ATTIVO ma le regole iptables non sono caricate → possibile perdita.
Soluzione: override systemd che forza l'ordine.
Crea /etc/systemd/system/wg-quick@wg0.service.d/killswitch.conf:
sudo mkdir -p /etc/systemd/system/wg-quick@wg0.service.d
sudo nano /etc/systemd/system/wg-quick@wg0.service.d/killswitch.conf
Contenuto:
[Unit]
After=netfilter-persistent.service
Wants=netfilter-persistent.service
Requires=netfilter-persistent.service
[Service]
# Se wg-quick si arresta, forza OUTPUT DROP per prevenire qualsiasi perdita
ExecStopPost=/sbin/iptables -P OUTPUT DROP
ExecStopPost=/sbin/ip6tables -P OUTPUT DROP
Ricarica systemd:
sudo systemctl daemon-reload
sudo systemctl enable wg-quick@wg0
sudo systemctl enable netfilter-persistent
sudo systemctl restart netfilter-persistent
sudo systemctl restart wg-quick@wg0
Ad ogni avvio, systemd avvia netfilter-persistent prima (iptables caricato), poi wg-quick@wg0 (tunnel attivo). Nessuna finestra di perdita.
Test di validazione
Test 1 — Interrompi il tunnel, verifica il silenzio
# Tunnel attivo
sudo wg show
# Deve mostrare un handshake recente
# Test accesso Internet
curl -s -m 5 https://ifconfig.me
# Deve restituire l'IP del VPS
# Interrompi tunnel
sudo wg-quick down wg0
# Riprova
curl -s -m 5 https://ifconfig.me
# Deve andare in timeout senza risposta — KILL SWITCH ATTIVO
Se vedi un IP al secondo comando → perdita, le regole iptables non sono applicate. Controlla sudo iptables -L -v -n e conferma che il default OUTPUT sia DROP.
Test 2 — Simula un crash di WireGuard
# Tunnel attivo
sudo wg-quick up wg0
ping -c 2 1.1.1.1
# Deve pingare OK
# Uccisione brutale del processo WireGuard (simula crash)
sudo pkill -9 wg
sudo ip link delete wg0 2>/dev/null
# Riprova
ping -c 2 1.1.1.1
# Deve fallire "Operazione non permessa"
Il kernel rifiuta il ping perché nessun percorso rimane consentito. Senza ExecStopPost nel drop-in systemd, alcune distribuzioni lasciano una finestra di perdita dopo il crash — quella linea è importante.
Test 3 — Perdita DNS
# Tunnel ATTIVO
dig @9.9.9.9 ifconfig.me +short
# Deve restituire l'IP del VPS
# Interrompi tunnel
sudo wg-quick down wg0
# Riprova
dig @9.9.9.9 ifconfig.me +short
# Deve andare in timeout / "nessun server raggiungibile"
Anche il DNS è bloccato fuori dal tunnel. Obiettivo raggiunto.
Casi speciali
Esegui diversi tunnel (wg0 + wg1)
Duplica l'eccezione OUTPUT:
-A OUTPUT -p udp -d 1.2.3.4 --dport 51820 -j ACCEPT # tunnel principale
-A OUTPUT -p udp -d 5.6.7.8 --dport 51820 -j ACCEPT # tunnel secondario
-A OUTPUT -o wg0 -j ACCEPT
-A OUTPUT -o wg1 -j ACCEPT
Esegui Tailscale in parallelo
Tailscale utilizza UDP 41641 di default. Aggiungi:
-A OUTPUT -p udp --dport 41641 -j ACCEPT
-A OUTPUT -o tailscale0 -j ACCEPT
Mantieni la LAN locale raggiungibile (NAS, stampante)
La regola template -A OUTPUT -d 192.168.1.0/24 -j ACCEPT lo fa già. Adatta la subnet.
Non vuoi SSH in entrata (workstation mobile)
Elimina la linea --dport 22 -m state --state NEW -j ACCEPT. La connessione esistente (ESTABLISHED) rimane OK.
Registrazione dei pacchetti scartati (debug)
Per vedere cosa sarebbe trapelato senza il kill switch:
sudo iptables -I OUTPUT 1 -j LOG --log-prefix "OUTPUT-DROP: " --log-level 4
Poi:
sudo journalctl -k -f | grep "OUTPUT-DROP"
Vedrai ogni pacchetto che il tuo OS tenta di inviare in chiaro quando il tunnel è giù (DNS Apple, NTP, heartbeat Spotify, ecc.). Educativo.
Una volta terminato il debug, rimuovi la regola LOG (altrimenti journalctl si riempie):
sudo iptables -D OUTPUT -j LOG --log-prefix "OUTPUT-DROP: " --log-level 4
Bonus: kill switch direttamente dentro wg0.conf
Se preferisci non gestire iptables separatamente, WireGuard accetta PostUp/PreDown che fanno lo stesso lavoro:
[Interface]
# ...
PostUp = iptables -I OUTPUT ! -o %i -m mark ! --mark $(wg show %i fwmark) -m addrtype ! --dst-type LOCAL -j REJECT
PostUp = ip6tables -I OUTPUT ! -o %i -m mark ! --mark $(wg show %i fwmark) -m addrtype ! --dst-type LOCAL -j REJECT
PreDown = iptables -D OUTPUT ! -o %i -m mark ! --mark $(wg show %i fwmark) -m addrtype ! --dst-type LOCAL -j REJECT
PreDown = ip6tables -D OUTPUT ! -o %i -m mark ! --mark $(wg show %i fwmark) -m addrtype ! --dst-type LOCAL -j REJECT
Pro: zero file extra. Contro: le regole svaniscono se WireGuard si arresta senza chiamare PreDown (panic del kernel, OOM kill). L'approccio systemd sopra descritto è più robusto per la produzione a lungo termine.
Vedi anche il template di kill-switch da viaggio nella guida WireGuard 2026 per la versione inline-wg0.conf.
Controllo finale
# Riavvio completo
sudo reboot
# Dopo il riavvio, nessun passaggio manuale
ip a show wg0
# Deve mostrare tunnel ATTIVO
iptables -L OUTPUT -v -n | head -5
# La politica predefinita deve essere DROP, i contatori di accettazione devono crescere
curl -s ifconfig.me
# Deve restituire l'IP del VPS
Tutto verde: il tuo kill switch è in atto, si avvia al boot, sopravvive ai crash. Puoi collegare la macchina a una rete ostile senza rischio di perdite.
Andare oltre
Il kill switch blocca le perdite. Ma per una vera tranquillità, aggiungi:
- Monitoraggio Prometheus + Grafana per la tua VPN VPS — stato del tunnel in tempo reale, throughput, peer connessi.
- Split-tunnel con tabelle di routing — utile quando alcune app dovrebbero bypassare la VPN (download Steam, Time Machine su NAS locale) mentre il resto rimane tunnelizzato.
- Template WireGuard pronti all'uso — 8 configurazioni testate in produzione.
- Generatore di configurazioni WireGuard — genera un
wg0.confcon regole di kill-switch PostUp/PreDown precompilate, senza bisogno di scrivere INI. - Miglior VPN self-hosted 2026 — confronta WireGuard con Tailscale e Headscale prima di impegnarti in uno stack.
E la base: Configurazione WireGuard su Contabo in 20 minuti.
★ Datacenter GDPR di Norimberga · ✓ IPv4 dedicato incluso · 200+ Mbps garantiti
Self-host your VPN on your own VPS → ContaboFull root access · public IPv4 · pick your region→