VPNSmith
self-host-vpnINFO

Kill switch VPN Linux con iptables y systemd (2026)

Kill switch netfilter que descarta tráfico fuera del túnel WireGuard. Servicio systemd ordenado, excepción interfaz VPN, test reconexión. Cero fugas garantizado.

Por Eric Gerard · Fondateur · VPNSmith — Spécialiste self-host VPN & VPS GDPR7 min de lecturaFoto vía Unsplash

Usas un VPN self-host. Te preguntas qué pasa si el túnel cae 12 segundos en medio de una subida sensible. En la mayoría de configs Linux por defecto: el tráfico pasa en claro por tu ruta por defecto, sin indicación visible. El DNS local se filtra, la IP real aparece en logs de servidores remotos. Un kill switch de aplicación no te salva — hay que bloquearlo a nivel kernel.

Esta guía monta un kill switch iptables + systemd que garantiza que nada sale de tu máquina en claro, incluso durante los 50 ms de cambio entre dos handshakes WireGuard.

El modelo que construimos

La idea central en 3 frases:

  1. Todo el tráfico OUTPUT está DROP por defecto salvo si sale por la interfaz VPN (wg0).
  2. Excepción: la conexión UDP hacia el servidor VPN (handshake) queda autorizada — sin esto el túnel no puede restablecerse tras una caída.
  3. systemd garantiza que esas reglas iptables se apliquen antes que WireGuard y sobrevivan al reinicio del túnel.

Resultado: si WireGuard muere, tu máquina literalmente no puede enviar nada salvo el handshake VPN. Ves "no connection" en navegador, pero ningún paquete se fuga.

Preparación

En Ubuntu/Debian (probado Ubuntu 22.04 y 24.04, kernel 5.15+):

sudo apt update
sudo apt install -y iptables iptables-persistent
# En prompt netfilter-persistent: YES para IPv4 y IPv6

Anota lo siguiente antes de continuar:

  • La IP pública del VPN: nslookup vpn.example.com o mira en wg0.conf la línea Endpoint.
  • El puerto UDP del VPN: normalmente 51820 para WireGuard.
  • La interfaz VPN: wg0 por defecto.
  • La subred LAN local a conservar accesible (192.168.1.0/24, 10.0.0.0/8, etc.) — para no romper impresora y NAS.

Reglas iptables IPv4

Crea /etc/iptables/rules.v4 con este contenido (adapta valores encajados):

*filter
:INPUT DROP [0:0]
:FORWARD DROP [0:0]
:OUTPUT DROP [0:0]

# Loopback siempre permitido
-A INPUT  -i lo -j ACCEPT
-A OUTPUT -o lo -j ACCEPT

# Conexiones ya establecidas (respuestas handshake VPN, etc.)
-A INPUT  -m state --state ESTABLISHED,RELATED -j ACCEPT
-A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT

# SSH entrante (si administras remoto — si no, quita estas 2 líneas)
-A INPUT  -p tcp --dport 22 -m state --state NEW -j ACCEPT

# Handshake VPN saliente: único tráfico autorizado fuera de túnel
-A OUTPUT -p udp -d 1.2.3.4 --dport 51820 -j ACCEPT

# DNS local en LAN (Pi-hole, router) — opcional
-A OUTPUT -d 192.168.1.0/24 -j ACCEPT

# Todo el resto del OUTPUT pasa por la interfaz VPN
-A OUTPUT -o wg0 -j ACCEPT

# Permitir paquetes entrantes en wg0
-A INPUT  -i wg0 -j ACCEPT

COMMIT

Reemplaza:

  • 1.2.3.4 por la IP pública real de tu servidor VPN
  • 51820 por tu puerto WireGuard
  • 192.168.1.0/24 por tu subred LAN
  • Quita la línea --dport 22 si no necesitas admin SSH entrante

Carga las reglas:

sudo iptables-restore < /etc/iptables/rules.v4
sudo iptables -L -v -n
# Verifica que los counters están a 0 en las chains DROP

Reglas ip6tables (CRÍTICO)

Sin IPv6 explícitamente bloqueado, muchas distros activan IPv6 por defecto y el tráfico se fuga. 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

# Si tu VPN soporta IPv6 (raro 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

# Si no, todo IPv6 fuera de loopback es DROP

COMMIT

Carga:

sudo ip6tables-restore < /etc/iptables/rules.v6

A este punto puedes probar: sin el túnel WireGuard, ningún ping a Internet pasa. Es lo esperado.

Servicio systemd para ordenar WireGuard tras iptables

Problema por defecto: wg-quick@wg0 puede arrancar antes que netfilter-persistent. Durante esa ventana (~1-3 segundos), el túnel está UP pero las reglas iptables no cargadas → fuga posible.

Solución: override systemd que fuerza el orden.

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

Contenido:

[Unit]
After=netfilter-persistent.service
Wants=netfilter-persistent.service
Requires=netfilter-persistent.service

[Service]
# Si wg-quick crashea, forzamos OUTPUT DROP para evitar fuga
ExecStopPost=/sbin/iptables -P OUTPUT DROP
ExecStopPost=/sbin/ip6tables -P OUTPUT DROP

Recarga 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

En cada boot, systemd arranca netfilter-persistent primero (reglas cargadas), luego wg-quick@wg0 (túnel montado). Sin ventana de fuga.

Tests de validación

Test 1 — Corta el túnel y verifica silencio

# Túnel up
sudo wg show
# Debe mostrar handshake reciente

# Probar acceso Internet
curl -s -m 5 https://ifconfig.me
# Debe devolver IP del VPS

# Cortar túnel
sudo wg-quick down wg0

# Re-test
curl -s -m 5 https://ifconfig.me
# Debe timeout sin respuesta — KILL SWITCH ACTIVO

Si ves IP en segundo comando → fuga, reglas no aplicadas. Verifica sudo iptables -L -v -n y confirma que OUTPUT default es DROP.

Test 2 — Simula un crash WireGuard

sudo wg-quick up wg0
ping -c 2 1.1.1.1
# Debe ping OK

# Kill brutal del proceso WireGuard
sudo pkill -9 wg
sudo ip link delete wg0 2>/dev/null

# Re-test
ping -c 2 1.1.1.1
# Debe fallar "Operation not permitted"

El kernel rechaza el ping porque ninguna ruta queda autorizada. Sin ExecStopPost en el drop-in systemd, algunas distros dejan una ventana de fuga tras el crash.

Test 3 — DNS leak

# Túnel UP
dig @9.9.9.9 ifconfig.me +short
# Debe devolver IP del VPS

# Corta túnel
sudo wg-quick down wg0

# Re-test
dig @9.9.9.9 ifconfig.me +short
# Debe timeout / "no servers could be reached"

Hasta el DNS está bloqueado fuera del túnel. Objetivo cumplido.

Casos especiales

Usas varios túneles (wg0 + wg1)

Duplica la excepción OUTPUT:

-A OUTPUT -p udp -d 1.2.3.4 --dport 51820 -j ACCEPT
-A OUTPUT -p udp -d 5.6.7.8 --dport 51820 -j ACCEPT
-A OUTPUT -o wg0 -j ACCEPT
-A OUTPUT -o wg1 -j ACCEPT

Tailscale en paralelo

Tailscale usa UDP 41641 por defecto. Añade:

-A OUTPUT -p udp --dport 41641 -j ACCEPT
-A OUTPUT -o tailscale0 -j ACCEPT

Conservar LAN local (NAS, impresora)

La regla -A OUTPUT -d 192.168.1.0/24 -j ACCEPT del template ya lo permite. Ajusta subred.

Logging de paquetes bloqueados (debug)

Para ver qué se habría fugado sin el kill switch:

sudo iptables -I OUTPUT 1 -j LOG --log-prefix "OUTPUT-DROP: " --log-level 4

Luego:

sudo journalctl -k -f | grep "OUTPUT-DROP"

Verás todos los paquetes que tu OS intenta enviar en claro cuando el túnel está down. Educativo.

Tras debug, elimina la regla LOG:

sudo iptables -D OUTPUT -j LOG --log-prefix "OUTPUT-DROP: " --log-level 4

Bonus: kill switch en wg0.conf directamente

Si prefieres no gestionar iptables separadamente, WireGuard acepta PostUp/PreDown que hacen el mismo trabajo:

[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

Ventaja: cero fichero extra. Inconveniente: las reglas desaparecen si WireGuard crashea sin llamar PreDown (kernel panic, OOM kill). El approach systemd es más robusto para prod long-term.

Ver también el template "travel kill switch" de la guía WireGuard 2026.

Verificación final

# Reboot completo
sudo reboot

# Tras reinicio, sin tocar nada
ip a show wg0
# Debe mostrar túnel UP

iptables -L OUTPUT -v -n | head -5
# Default policy debe ser DROP, accept counters deben crecer

curl -s ifconfig.me
# Debe devolver IP del VPS

Todo verde: kill switch en su sitio, funcional al boot, sobrevive a crashes. Puedes conectar la máquina en red hostil sin riesgo de fuga.

Para profundizar

El kill switch corta las fugas. Pero para estar tranquilo de verdad, añade:

Y la base: setup WireGuard sobre Contabo en 20 min.

★ Datacenter Nuremberg GDPR · ✓ IPv4 dédiée incluse · 200+ Mbps garantis

Probar Contabo30 jours satisfait ou remboursé