VPNSmith
self-host-vpnINFO

Kill switch VPN Linux con iptables y systemd (2026)

Kill switch netfilter que descarta el tráfico fuera del túnel WireGuard. Servicio systemd ordenado, tests de fuga e integración resolvconf 2026.

Por Eric Gerard · Fundador · VPNSmith - Especialista en VPN self-host y VPS GDPR9 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.

Por qué el kill switch netfilter es la única garantía seria

La industria comercial del VPN habla de «kill switch» desde hace siete años, pero la mayoría de las implementaciones son frágiles porque funcionan a nivel de aplicación: la app NordVPN o ExpressVPN detecta una caída del túnel y corta el tráfico interviniendo en la tabla de enrutamiento. El problema: durante los 200 a 800 milisegundos que la aplicación necesita para detectar la caída y reaccionar, los paquetes salen en claro por la interfaz por defecto. Es suficiente para que una conexión BitTorrent activa exponga tu IP real al tracker, o para que un upload S3 multipart en curso revele tu origen geográfico.

El kill switch netfilter (iptables o nftables) elude este problema poniendo la regla de filtrado en el kernel Linux mismo, no en un proceso user-space. Los paquetes atraviesan el subsystem netfilter en cada syscall send/sendmsg/sendto - no hay ventana de latencia aplicativa. Si WireGuard se cuelga, los paquetes siguen siendo DROP-eados por netfilter hasta que el túnel vuelva, independientemente del estado del proceso WireGuard. Por eso Mullvad recomienda netfilter como segunda capa además de su app, y por eso las guías Tor Project mencionan explícitamente el kill switch netfilter para los hosts que alojan relays.

Nuestra elección de iptables en lugar de nftables se debe al soporte: Ubuntu Server 22.04 LTS y Debian 12 todavía tienen iptables como herramienta de facto aunque el backend sea nftables por debajo. Los comandos iptables son copiables-pegables desde cualquier doc de los últimos 10 años, lo que reduce el riesgo de typo y facilita el mantenimiento. Si arrancas en 2026 sobre un host nuevo y dominas nftables, ve directo a nftables - la sintaxis es más limpia y el orden de las chains es explícito.

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

Racks de servidores iluminados en azul en un centro de datos
Racks de servidores iluminados en azul en un centro de datos

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 Núremberg GDPR · ✓ IPv4 dedicada incluida · 200+ Mbps garantizados

Aloja tu VPN en tu propio VPS → ContaboAcceso root completo · IPv4 pública · elige tu región

Preguntas frecuentes

¿Por qué un kill switch netfilter en lugar de aplicación?
Un kill switch de aplicación (Mullvad app, NordVPN app) corta el tráfico de la aplicación VPN, pero si crashea o es matada por el OS, el tráfico se fuga. Netfilter aplica reglas en kernel - sin bypass posible sin root.
¿El kill switch cubre IPv6?
Solo si añades las reglas ip6tables equivalentes (la guía las incluye). Sin esto, el tráfico IPv6 se filtra por defecto en la mayoría de distros recientes.
¿Impacto en performance?
Insignificante. Netfilter en modo -j REJECT consume <0,05% CPU a 1 Gbps. Sin pérdida de débito ni latencia añadida medible.