Tu utilises un VPN self-host. Tu te demandes ce qui se passe si le tunnel tombe pendant 12 secondes au milieu d'un upload sensible. Sur la plupart des configurations par défaut Linux : le trafic passe en clair par ta route par défaut, sans aucune indication visible. Le DNS local fuite, l'IP réelle apparaît dans les logs des serveurs distants. Un kill switch applicatif ne te sauve pas — il faut bloquer ça au niveau kernel.
Ce guide montre une configuration kill switch iptables + systemd qui garantit que rien ne sort de ta machine en clair, même pendant les 50 ms de bascule entre deux handshakes WireGuard.
Le modèle qu'on construit
L'idée centrale en 3 phrases :
- Tout le trafic OUTPUT est DROP par défaut sauf si il sort par l'interface VPN (
wg0). - Exception : la connexion UDP vers le serveur VPN (handshake) reste autorisée — sans ça le tunnel ne peut pas se rétablir après une coupure.
- systemd garantit que ces règles iptables sont appliquées avant WireGuard et survivent au redémarrage du tunnel.
Le résultat : si WireGuard plante, ta machine ne peut littéralement plus rien envoyer sur Internet sauf le handshake VPN. Tu vois "no connection" dans le navigateur, mais aucun paquet ne fuite.
Préparation
Sur Ubuntu/Debian (testé Ubuntu 22.04 et 24.04, kernel 5.15+) :
sudo apt update
sudo apt install -y iptables iptables-persistent
# Au prompt netfilter-persistent : YES pour IPv4 et IPv6
Note les éléments suivants avant de continuer :
- L'IP publique du VPN :
nslookup vpn.example.comou regarde danswg0.confla ligneEndpoint. - Le port UDP du VPN : généralement
51820pour WireGuard. - L'interface VPN :
wg0par défaut, peut êtrewg1si tu as plusieurs tunnels. - Le subnet du LAN local à conserver accessible (192.168.1.0/24, 10.0.0.0/8, etc.) — pour ne pas casser l'imprimante et le NAS.
Règles iptables IPv4
Crée /etc/iptables/rules.v4 avec ce contenu (adapte les valeurs encadrées) :
*filter
:INPUT DROP [0:0]
:FORWARD DROP [0:0]
:OUTPUT DROP [0:0]
# Loopback toujours autorisé
-A INPUT -i lo -j ACCEPT
-A OUTPUT -o lo -j ACCEPT
# Connexions déjà établies (réponses au handshake VPN, etc.)
-A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
-A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
# SSH entrant (si tu administres à distance — sinon supprime ces 2 lignes)
-A INPUT -p tcp --dport 22 -m state --state NEW -j ACCEPT
# Handshake VPN sortant : seul trafic autorisé hors tunnel
-A OUTPUT -p udp -d 1.2.3.4 --dport 51820 -j ACCEPT
# DNS local sur LAN (Pi-hole, routeur) — optionnel
-A OUTPUT -d 192.168.1.0/24 -j ACCEPT
# Tout le reste du trafic OUTPUT passe par l'interface VPN
-A OUTPUT -o wg0 -j ACCEPT
# Permettre les paquets entrants sur wg0
-A INPUT -i wg0 -j ACCEPT
COMMIT
Remplace :
1.2.3.4par l'IP publique réelle de ton serveur VPN51820par ton port WireGuard192.168.1.0/24par ton subnet LAN- Supprime la ligne
--dport 22si tu n'as pas besoin d'admin SSH entrant
Charge les règles :
sudo iptables-restore < /etc/iptables/rules.v4
sudo iptables -L -v -n
# Vérifier que les compteurs sont à 0 sur les chains DROP
Règles ip6tables (CRITIQUE)
Sans IPv6 explicitement bloqué, beaucoup de distros activent IPv6 par défaut et le trafic fuite. Crée /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
# Si ton VPN supporte IPv6 (rare en 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
# Sinon, tout IPv6 hors loopback est DROP
COMMIT
Charge :
sudo ip6tables-restore < /etc/iptables/rules.v6
À ce stade tu peux tester : sans le tunnel WireGuard, aucun ping vers Internet ne passe. C'est attendu.
Service systemd pour ordonner WireGuard après iptables
Le souci par défaut : wg-quick@wg0 peut démarrer avant netfilter-persistent. Pendant cette fenêtre (~1-3 secondes), le tunnel est UP mais les règles iptables ne sont pas chargées → fuite possible.
Solution : override systemd qui force l'ordre.
Crée /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
Contenu :
[Unit]
After=netfilter-persistent.service
Wants=netfilter-persistent.service
Requires=netfilter-persistent.service
[Service]
# Si wg-quick plante, on force le DROP de OUTPUT pour éviter toute fuite
ExecStopPost=/sbin/iptables -P OUTPUT DROP
ExecStopPost=/sbin/ip6tables -P OUTPUT DROP
Recharge 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
À chaque boot, systemd démarre netfilter-persistent d'abord (règles iptables chargées), puis wg-quick@wg0 (tunnel monté). Pas de fenêtre de fuite.
Tests de validation
Test 1 — Coupe le tunnel et vérifie le silence
# Tunnel up
sudo wg show
# Doit montrer une dernière handshake récente
# Tester accès Internet
curl -s -m 5 https://ifconfig.me
# Doit retourner l'IP du VPS
# Couper le tunnel
sudo wg-quick down wg0
# Re-tester
curl -s -m 5 https://ifconfig.me
# Doit timeout sans réponse — KILL SWITCH ACTIF
Si tu vois une IP retournée à la deuxième commande → fuite, les règles iptables ne sont pas appliquées. Vérifie sudo iptables -L -v -n et regarde si OUTPUT est bien DROP par défaut.
Test 2 — Simule un crash WireGuard
# Tunnel up
sudo wg-quick up wg0
ping -c 2 1.1.1.1
# Doit ping OK
# Kill brutal du processus WireGuard (simule crash)
sudo pkill -9 wg
sudo ip link delete wg0 2>/dev/null
# Re-test
ping -c 2 1.1.1.1
# Doit fail "Operation not permitted"
Le kernel a refusé le ping car aucune route ne reste autorisée. Sans le ExecStopPost dans le drop-in systemd, certaines distros laissent une fenêtre de fuite après le crash — d'où l'importance de cette ligne.
Test 3 — DNS leak
# Avec le tunnel UP
dig @9.9.9.9 ifconfig.me +short
# Doit retourner l'IP du VPS
# Coupe le tunnel
sudo wg-quick down wg0
# Re-test
dig @9.9.9.9 ifconfig.me +short
# Doit timeout / "no servers could be reached"
Même le DNS est bloqué hors tunnel. C'est l'objectif.
Cas spéciaux
Tu utilises plusieurs tunnels (wg0 + wg1)
Duplique l'exception OUTPUT :
-A OUTPUT -p udp -d 1.2.3.4 --dport 51820 -j ACCEPT # tunnel principal
-A OUTPUT -p udp -d 5.6.7.8 --dport 51820 -j ACCEPT # tunnel secondaire
-A OUTPUT -o wg0 -j ACCEPT
-A OUTPUT -o wg1 -j ACCEPT
Tu veux exempter Tailscale en parallèle
Tailscale utilise UDP 41641 par défaut. Ajoute :
-A OUTPUT -p udp --dport 41641 -j ACCEPT
-A OUTPUT -o tailscale0 -j ACCEPT
Le LAN local doit rester accessible (NAS, imprimante)
La règle -A OUTPUT -d 192.168.1.0/24 -j ACCEPT du template le permet déjà. Adapte le subnet si nécessaire.
Tu ne veux pas SSH entrant (poste mobile)
Supprime la ligne --dport 22 -m state --state NEW -j ACCEPT. La connexion existante (déjà ESTABLISHED) reste OK.
Logging des paquets bloqués (debug)
Pour voir ce qui aurait fuit sans le kill switch :
sudo iptables -I OUTPUT 1 -j LOG --log-prefix "OUTPUT-DROP: " --log-level 4
Puis :
sudo journalctl -k -f | grep "OUTPUT-DROP"
Tu verras tous les paquets que ton OS tente d'envoyer en clair quand le tunnel est down (DNS Apple, NTP, Spotify heartbeat, etc.). C'est instructif.
Une fois le debug terminé, supprime la règle LOG (sinon ça remplit journalctl) :
sudo iptables -D OUTPUT -j LOG --log-prefix "OUTPUT-DROP: " --log-level 4
Bonus : kill switch dans le wg0.conf directement
Si tu préfères ne pas gérer iptables séparément, WireGuard accepte des PostUp/PreDown qui font le même travail :
[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
Avantage : zéro fichier supplémentaire. Inconvénient : les règles disparaissent si WireGuard crash sans appeler PreDown (kernel panic, OOM kill). L'approche systemd ci-dessus est plus robuste pour de la production long-terme.
Voir aussi le template "travel kill switch" du guide WireGuard 2026 pour la version embarquée dans wg0.conf.
Vérification finale
# Reboot complet
sudo reboot
# Au redémarrage, sans rien faire
ip a show wg0
# Doit montrer le tunnel UP
iptables -L OUTPUT -v -n | head -5
# Default policy doit être DROP, accept counters doivent croître
curl -s ifconfig.me
# Doit retourner l'IP du VPS
Si tout est vert : ton kill switch est en place, fonctionne au boot, survit aux crashes. Tu peux mettre la machine sur un réseau hostile sans risque de fuite.
Pour aller plus loin
Le kill switch coupe les fuites. Mais pour vraiment être tranquille, ajoute :
- Monitoring Prometheus + Grafana pour ton VPN VPS — tu sais en temps réel si le tunnel est UP, le débit, les peers connectés.
- Split-tunnel avec tables de routage — utile si tu veux que certaines apps passent en clair (téléchargements Steam, sauvegarde Time Machine sur NAS local) tout en gardant le reste tunnelé.
- Templates WireGuard prêts à l'emploi — 8 configurations testées en prod.
Et la base : setup WireGuard sur Contabo en 20 minutes.
★ Datacenter Nuremberg GDPR · ✓ IPv4 dédiée incluse · 200+ Mbps garantis
Voir l'offre Contabo30 jours satisfait ou remboursé→