A VPN kill switch is a firewall rule that blocks all internet traffic when the VPN connection drops. Without one, your real IP address is exposed the moment the VPN disconnects — even briefly. Many commercial VPN clients include a kill switch, but it often has gaps: it may not activate during initial connection, it may not survive reboots, or it may only block certain traffic types.
Implementing a kill switch at the Linux firewall level gives you control that no VPN client setting can. This guide covers both iptables and nftables implementations.
How a Kill Switch Works
The logic is simple: allow traffic only through the VPN tunnel interface. Block everything else.
With this rule set:
- Before connecting to VPN: no external traffic is allowed
- While connected: traffic flows normally through the VPN tunnel
- If VPN drops: the tunnel interface disappears, traffic matches the “deny all” rule, nothing leaks
You must know:
- Your VPN tunnel interface name (usually
tun0,wg0, orproton0) - Your VPN server’s IP address (needed for the initial connection to establish the tunnel)
- Your local network interface name (usually
eth0orwlan0)
# Find interface names
ip link show
ip route show
Method 1: iptables Kill Switch
Basic kill switch rules
#!/bin/bash
# vpn-killswitch.sh
# Replace variables with your actual values
VPN_INTERFACE="wg0" # Your VPN tunnel interface (wg0, tun0, etc.)
LOCAL_INTERFACE="eth0" # Your physical network interface
VPN_SERVER_IP="203.0.113.50" # Your VPN server's IP address
VPN_PORT="51820" # VPN protocol port (51820=WireGuard, 1194=OpenVPN UDP)
VPN_PROTOCOL="udp" # udp or tcp
# Flush existing rules
iptables -F
iptables -X
# Default policies: DROP everything
iptables -P INPUT DROP
iptables -P OUTPUT DROP
iptables -P FORWARD DROP
# Allow loopback
iptables -A INPUT -i lo -j ACCEPT
iptables -A OUTPUT -o lo -j ACCEPT
# Allow established/related connections
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
# Allow ONLY the VPN connection itself to pass through the physical interface
iptables -A OUTPUT -o "$LOCAL_INTERFACE" -d "$VPN_SERVER_IP" -p "$VPN_PROTOCOL" --dport "$VPN_PORT" -j ACCEPT
# Allow all traffic through the VPN tunnel interface
iptables -A INPUT -i "$VPN_INTERFACE" -j ACCEPT
iptables -A OUTPUT -o "$VPN_INTERFACE" -j ACCEPT
# Allow local network traffic (optional — comment out for maximum isolation)
iptables -A INPUT -i "$LOCAL_INTERFACE" -s 192.168.0.0/16 -j ACCEPT
iptables -A OUTPUT -o "$LOCAL_INTERFACE" -d 192.168.0.0/16 -j ACCEPT
# Allow DHCPv4 (needed to get an IP from your router)
iptables -A INPUT -i "$LOCAL_INTERFACE" -p udp --sport 67 --dport 68 -j ACCEPT
iptables -A OUTPUT -o "$LOCAL_INTERFACE" -p udp --sport 68 --dport 67 -j ACCEPT
echo "Kill switch enabled. All traffic blocked except VPN tunnel."
chmod +x vpn-killswitch.sh
sudo ./vpn-killswitch.sh
Disable the kill switch
#!/bin/bash
# vpn-killswitch-disable.sh
iptables -F
iptables -X
iptables -P INPUT ACCEPT
iptables -P OUTPUT ACCEPT
iptables -P FORWARD ACCEPT
echo "Kill switch disabled. Normal routing restored."
Persist rules across reboots
# Install iptables-persistent
sudo apt install iptables-persistent
# Save current rules
sudo iptables-save | sudo tee /etc/iptables/rules.v4
# Rules are automatically loaded on boot by the netfilter-persistent service
sudo systemctl enable netfilter-persistent
Method 2: nftables Kill Switch (Modern Linux Systems)
nftables is the replacement for iptables on modern Linux distributions. Debian 10+, Ubuntu 20.04+, Fedora 32+, and Arch all prefer nftables.
# Check if nftables is active
sudo nft list ruleset
Create /etc/nftables-killswitch.conf:
#!/usr/sbin/nft -f
flush ruleset
define VPN_INTERFACE = wg0
define LOCAL_INTERFACE = eth0
define VPN_SERVER = 203.0.113.50
define VPN_PORT = 51820
table inet filter {
chain input {
type filter hook input priority 0; policy drop;
# Accept loopback
iif "lo" accept
# Accept established connections
ct state established,related accept
# Accept traffic from VPN tunnel
iifname $VPN_INTERFACE accept
# Accept DHCP
iifname $LOCAL_INTERFACE udp dport 68 accept
# Accept local network
iifname $LOCAL_INTERFACE ip saddr 192.168.0.0/16 accept
}
chain output {
type filter hook output priority 0; policy drop;
# Accept loopback
oif "lo" accept
# Accept traffic through VPN tunnel
oifname $VPN_INTERFACE accept
# Accept ONLY VPN connection traffic on physical interface
oifname $LOCAL_INTERFACE ip daddr $VPN_SERVER udp dport $VPN_PORT accept
# Accept local network
oifname $LOCAL_INTERFACE ip daddr 192.168.0.0/16 accept
# Accept DHCP
oifname $LOCAL_INTERFACE udp sport 68 dport 67 accept
}
chain forward {
type filter hook forward priority 0; policy drop;
}
}
Apply the rules:
sudo nft -f /etc/nftables-killswitch.conf
# To persist: enable nftables service
sudo systemctl enable nftables
sudo cp /etc/nftables-killswitch.conf /etc/nftables.conf
Method 3: NetworkManager Kill Switch (for Desktop Users)
If you use NetworkManager and a VPN that supports it:
# Via nmcli — add a kill switch to an existing VPN connection
nmcli connection modify "MyVPN" ipv4.never-default no
nmcli connection modify "MyVPN" connection.autoconnect-retries -1
# Create a dispatcher script to block traffic when VPN is down
sudo nano /etc/NetworkManager/dispatcher.d/99-vpn-killswitch
#!/bin/bash
# /etc/NetworkManager/dispatcher.d/99-vpn-killswitch
INTERFACE=$1
STATUS=$2
case "$STATUS" in
vpn-down)
iptables -I OUTPUT ! -o lo -j REJECT --reject-with icmp-port-unreachable
;;
vpn-up)
iptables -D OUTPUT ! -o lo -j REJECT --reject-with icmp-port-unreachable
;;
esac
sudo chmod +x /etc/NetworkManager/dispatcher.d/99-vpn-killswitch
Testing the Kill Switch
# Step 1: Enable kill switch but do NOT connect to VPN
sudo ./vpn-killswitch.sh
# Step 2: Try to reach the internet — should fail
curl -s --max-time 5 https://api.ipify.org
# Should time out or return an error
# Step 3: Connect to VPN
wg-quick up wg0 # For WireGuard
# or: openvpn --config client.ovpn
# Step 4: Verify connectivity through VPN
curl -s https://api.ipify.org
# Should return VPN server's IP, not yours
# Step 5: Simulate VPN drop
sudo ip link set wg0 down
# Step 6: Verify traffic is blocked again
curl -s --max-time 5 https://api.ipify.org
# Should fail — kill switch working correctly
IPv6 Leak Prevention
IPv6 traffic can bypass IPv4 kill switch rules. Block it explicitly:
# Disable IPv6 entirely (simplest approach)
sudo sysctl -w net.ipv6.conf.all.disable_ipv6=1
sudo sysctl -w net.ipv6.conf.default.disable_ipv6=1
# Or block IPv6 with ip6tables kill switch
sudo ip6tables -P INPUT DROP
sudo ip6tables -P OUTPUT DROP
sudo ip6tables -P FORWARD DROP
sudo ip6tables -A INPUT -i lo -j ACCEPT
sudo ip6tables -A OUTPUT -o lo -j ACCEPT
sudo ip6tables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
# Add VPN IPv6 exceptions if your VPN uses IPv6
Related Reading
- How to Test That Your VPN Kill Switch Actually Works
- VPN Kill Switch: How It Works and Which VPNs Have Real Ones
- How to Use WireGuard for a Self-Hosted VPN 2026
Built by theluckystrike — More at zovo.one