VPNSmith
self-host-vpnINFO

Kill switch VPN su Linux con iptables e systemd (2026)

Kill switch Netfilter che blocca tutto il traffico WireGuard fuori dal tunnel. Servizio systemd ordinato, eccezione per l'interfaccia VPN, test di riconnessione. Zero perdite garantite.

Di Eric Gerard · Fondateur · VPNSmith — Spécialiste self-host VPN & VPS GDPR9 min letturaPhoto via Unsplash

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:

  1. Tutto il traffico OUTPUT è DROP di default a meno che non esca tramite l'interfaccia VPN (wg0).
  2. Eccezione: la connessione UDP al server VPN (handshake) rimane consentita — senza di essa, il tunnel non può ristabilirsi dopo una caduta.
  3. 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.com o controlla la linea Endpoint in wg0.conf.
  • La porta UDP della VPN: solitamente 51820 per WireGuard.
  • L'interfaccia VPN: wg0 di default, può essere wg1 con 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.4 con l'effettivo IP pubblico del tuo server VPN
  • 51820 con la tua porta WireGuard
  • 192.168.1.0/24 con la tua subnet LAN
  • Elimina la linea --dport 22 se 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

Server racks illuminati di blu in un data center
Server racks illuminati di blu in un data center

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:

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