Privacy Tools Guide

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:

You must know:

  1. Your VPN tunnel interface name (usually tun0, wg0, or proton0)
  2. Your VPN server’s IP address (needed for the initial connection to establish the tunnel)
  3. Your local network interface name (usually eth0 or wlan0)
# 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

Built by theluckystrike — More at zovo.one