WireGuard Kill Switch (IPv4): Block All Traffic If the VPN Drops
Summary: Configure WireGuard with a kill switch that blocks all IPv4 internet traffic whenever the VPN tunnel is down, preventing any data from leaking outside the encrypted connection. This tutorial covers IPv4 only — IPv6 traffic is not addressed.
| Key | Value |
|---|---|
| Server public IP | 203.0.113.10 |
| Client VPN IP | 10.0.0.2 |
| Server VPN IP | 10.0.0.1 |
| WireGuard port | 51820 |
| DNS server | 1.1.1.1 |
| Firewall mark | 51820 |
| Custom routing table | 51820 |
| Config path | /etc/wireguard/wg0.conf |
| OS | Ubuntu 24.04 LTS |
0. Prerequisites
- A working WireGuard client connection (see Tutorial 01: WireGuard Client Setup)
sudoor root accessiptablesinstalled (included by default on Ubuntu)- Familiarity with CIDR notation and routing basics (see Tutorial 00: Networking Basics for a refresher)
Warning: This tutorial covers IPv4 only. All blackhole routes and
iptablesrules target IPv4 addresses. If your system has IPv6 enabled, IPv6 traffic is not blocked by these rules and could leak outside the tunnel. To prevent IPv6 leaks, disable IPv6 on the client:
sudo sysctl -w net.ipv6.conf.all.disable_ipv6=1To make this persistent across reboots, add
net.ipv6.conf.all.disable_ipv6=1to/etc/sysctl.d/99-disable-ipv6.confand runsudo sysctl --system.
1. What Is a Kill Switch?
A kill switch ensures that if the VPN connection drops, no traffic leaves your machine unencrypted. Without a kill switch, a VPN disconnection silently reverts to your regular internet connection, exposing your real IP address and sending traffic in the clear.
The kill switch in this tutorial uses three mechanisms:
- Blackhole route — a routing table entry that silently drops all packets matching the default route (
0.0.0.0/0— every address), so nothing can reach the internet - Policy routing with
FwMark— marks VPN packets with a firewall tag so only they are allowed to bypass the blackhole iptablesfirewall rules — blocks traffic to local private networks (like192.168.x.x), preventing LAN leaks
Note: This configuration blocks local network access entirely. If you need to reach local devices (NAS, printers) while the VPN is active, see the “Allowing Local Access” section at the end of this tutorial.
2. How the Kill Switch Works
2.1 FwMark and Table
FwMark = 51820
Table = 51820
FwMark = 51820 tells WireGuard to mark its own encrypted packets with firewall mark 51820. These are the outer UDP packets that carry the tunnel traffic to the server.
Table = 51820 tells wg-quick to add VPN routes to custom routing table 51820 instead of the main table. This isolates VPN routing from the system’s normal routing.
Note: In Tutorials 01 and 02, the config omits
TableandFwMark, sowg-quickdefaults toTable = autoand manages policy routing rules (ip rules) automatically. SettingTable = 51820explicitly changes this behavior —wg-quickwill only add routes to that table, leaving ip rule management to thePostUpandPreDownhooks. The kill switch needs this manual control so thatPreDowncan remove the routing rules and install the blackhole before the interface goes down.
Together, these two settings are what keep the VPN endpoint reachable even when the blackhole route is active. WireGuard’s encrypted UDP packets to 203.0.113.10 carry the 51820 mark, so the policy rule (not fwmark 51820 table 51820) does not divert them into the custom table. They stay in the main table, which still has a route to 203.0.113.10 through your regular gateway. Unmarked traffic hits the blackhole; marked VPN traffic does not.
You can verify this once the tunnel is running:
ip route get 203.0.113.10 mark 0xca6c
Code language: CSS (css)
The output should show the packet leaving via your physical interface (e.g., eth0) and your local gateway — not wg0. This confirms that WireGuard’s marked packets bypass the kill switch.
2.2 PostUp Rules (Tunnel Comes Up)
When the tunnel starts, PostUp runs these commands:
Route VPN-marked traffic through the custom table:
ip rule add not fwmark 51820 table 51820
Any packet without the 51820 mark gets looked up in table 51820. Since table 51820 has a default route through wg0, unmarked traffic goes through the tunnel.
Suppress the main table’s default route:
ip rule add table main suppress_prefixlength 0
This prevents the main routing table’s default route (prefix length /0) from being used. Without this, traffic could escape through the regular internet gateway.
Remove the blackhole route:
ip route del blackhole 0.0.0.0/0 || true
Code language: JavaScript (javascript)
While the tunnel is active, the blackhole route is not needed — traffic flows through the VPN. The || true prevents an error if the blackhole route does not exist yet (e.g., on first boot).
2.3 PreDown Rules (Tunnel Goes Down)
When the tunnel is about to stop, PreDown reverses the PostUp rules and installs the blackhole:
Remove the routing rules:
ip rule del not fwmark 51820 table 51820
ip rule del table main suppress_prefixlength 0
Install the blackhole route:
ip route add blackhole 0.0.0.0/0 || true
Code language: JavaScript (javascript)
This is the kill switch. With the VPN down and a blackhole as the default route, all internet traffic is dropped. Nothing leaks.
2.4 Firewall Rules (iptables)
The blackhole route blocks internet traffic, but traffic to local private networks (like 192.168.1.0/24) uses more specific routes that are not affected by the blackhole. To prevent LAN leaks, iptables rules drop all traffic to private address ranges unless it exits through the VPN tunnel:
iptables -A OUTPUT ! -o wg0 -d 10.0.0.0/8 -j DROP
iptables -A OUTPUT ! -o wg0 -d 172.16.0.0/12 -j DROP
iptables -A OUTPUT ! -o wg0 -d 192.168.0.0/16 -j DROP
The ! -o wg0 condition means “any packet NOT going out through the wg0 interface.” When the VPN is active, traffic to VPN addresses (like 10.0.0.1) goes through wg0 and is allowed. Traffic to your LAN (like 192.168.1.100) goes through eth0 and is dropped.
When the VPN is down, wg0 does not exist — so every packet matches ! -o wg0 and all private network traffic is dropped. Combined with the blackhole route for internet traffic, nothing leaks.
Note: The
10.0.0.0/8rule also covers the VPN subnet (10.0.0.0/24). This is intentional — when the tunnel is down, there is no reason to send traffic to VPN addresses, and blocking it prevents any accidental leaks to the VPN range via the physical interface.
Note: These rules are installed once and persist across VPN up/down cycles. They are not added in
PostUpor removed inPreDown.
3. The Configuration File
Create or replace /etc/wireguard/wg0.conf:
sudo nano /etc/wireguard/wg0.conf
[Interface]
PrivateKey = <CLIENT_PRIVATE_KEY>
Address = 10.0.0.2/32
DNS = 1.1.1.1
FwMark = 51820
Table = 51820
PostUp = ip rule add not fwmark 51820 table 51820; ip rule add table main suppress_prefixlength 0; ip route del blackhole 0.0.0.0/0 || true
PreDown = ip rule del not fwmark 51820 table 51820; ip rule del table main suppress_prefixlength 0; ip route add blackhole 0.0.0.0/0 || true
[Peer]
PublicKey = <SERVER_PUBLIC_KEY>
Endpoint = 203.0.113.10:51820
AllowedIPs = 0.0.0.0/0
PersistentKeepalive = 25
Code language: HTML, XML (xml)
Replace <CLIENT_PRIVATE_KEY> and <SERVER_PUBLIC_KEY> with your actual keys.
Lock down permissions:
sudo chmod 600 /etc/wireguard/wg0.conf
4. Install the Kill Switch Rules
Before starting the tunnel for the first time, install the blackhole route and firewall rules so the kill switch is active even before the VPN has ever connected.
Warning: If you are connected to this machine over SSH, running these commands will immediately cut your session. Install the kill switch from a local console or a direct keyboard/monitor connection. If you must use SSH, combine the install and tunnel-up steps into one command so that connectivity is restored immediately:
sudo ip route replace blackhole 0.0.0.0/0 && sudo wg-quick up wg0.
Add the blackhole route to block internet traffic:
sudo ip route replace blackhole 0.0.0.0/0
Add iptables rules to block LAN traffic. The -C (check) flag tests whether the rule already exists, so running these commands more than once will not create duplicates:
sudo iptables -C OUTPUT ! -o wg0 -d 10.0.0.0/8 -j DROP 2>/dev/null || sudo iptables -A OUTPUT ! -o wg0 -d 10.0.0.0/8 -j DROP
sudo iptables -C OUTPUT ! -o wg0 -d 172.16.0.0/12 -j DROP 2>/dev/null || sudo iptables -A OUTPUT ! -o wg0 -d 172.16.0.0/12 -j DROP
sudo iptables -C OUTPUT ! -o wg0 -d 192.168.0.0/16 -j DROP 2>/dev/null || sudo iptables -A OUTPUT ! -o wg0 -d 192.168.0.0/16 -j DROP
Code language: JavaScript (javascript)
Warning: After running these commands, you will have no internet or local network access until the VPN tunnel comes up. This is intentional — the kill switch is now active.
5. Start the Tunnel
sudo wg-quick up wg0
The PostUp commands fire: the blackhole route is removed, policy routing rules are added, and traffic flows through the VPN.
Enable the tunnel at boot:
sudo systemctl enable wg-quick@wg0
Code language: CSS (css)
6. Test the Kill Switch
6.1 Verify VPN Is Working
curl ifconfig.me
Code language: CSS (css)
The output should show 203.0.113.10 (the VPN server’s IP).
6.2 Simulate a VPN Drop
Bring the tunnel down:
sudo wg-quick down wg0
The PreDown commands fire: routing rules are removed and the blackhole route is installed.
Now test:
curl --max-time 5 ifconfig.me
Code language: CSS (css)
This should time out. No traffic can reach the internet — the kill switch is working.
6.3 Restore Connectivity
Bring the tunnel back up:
sudo wg-quick up wg0
Internet access resumes through the VPN.
7. Making the Kill Switch Persistent Across Reboots
The blackhole route and iptables rules added in Step 4 do not survive a reboot. To ensure the kill switch is active from the moment the system boots (before WireGuard starts), create a systemd service:
sudo nano /etc/systemd/system/vpn-killswitch.service
[Unit]
Description=VPN Kill Switch - Blackhole default route and LAN firewall
[email protected]
DefaultDependencies=no
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/sbin/ip route replace blackhole 0.0.0.0/0
ExecStart=/bin/sh -c '/usr/sbin/iptables -C OUTPUT ! -o wg0 -d 10.0.0.0/8 -j DROP 2>/dev/null || /usr/sbin/iptables -A OUTPUT ! -o wg0 -d 10.0.0.0/8 -j DROP'
ExecStart=/bin/sh -c '/usr/sbin/iptables -C OUTPUT ! -o wg0 -d 172.16.0.0/12 -j DROP 2>/dev/null || /usr/sbin/iptables -A OUTPUT ! -o wg0 -d 172.16.0.0/12 -j DROP'
ExecStart=/bin/sh -c '/usr/sbin/iptables -C OUTPUT ! -o wg0 -d 192.168.0.0/16 -j DROP 2>/dev/null || /usr/sbin/iptables -A OUTPUT ! -o wg0 -d 192.168.0.0/16 -j DROP'
ExecStop=/bin/sh -c '/usr/sbin/iptables -D OUTPUT ! -o wg0 -d 192.168.0.0/16 -j DROP || true'
ExecStop=/bin/sh -c '/usr/sbin/iptables -D OUTPUT ! -o wg0 -d 172.16.0.0/12 -j DROP || true'
ExecStop=/bin/sh -c '/usr/sbin/iptables -D OUTPUT ! -o wg0 -d 10.0.0.0/8 -j DROP || true'
ExecStop=/bin/sh -c '/usr/sbin/ip route del blackhole 0.0.0.0/0 || true'
[Install]
WantedBy=multi-user.target
Code language: JavaScript (javascript)
Enable the service:
sudo systemctl enable vpn-killswitch.service
Code language: CSS (css)
This ensures the blackhole route and iptables rules are in place before the WireGuard tunnel starts, closing the window where traffic could leak during boot.
Note:
Type=oneshotallows multipleExecStartlines — systemd runs them in order. TheExecStoplines run in order when the service stops. Theip route replaceandiptables -C ... || iptables -A ...patterns make every command idempotent — the service can be restarted safely without creating duplicate routes or firewall rules.
8. Allowing Local Network Access
The configuration above blocks all non-VPN traffic, including traffic to local devices. If you need to access devices on your LAN (NAS, printers) while the VPN is active, add an iptables exception and a route for your local subnet in PostUp and remove them in PreDown:
PostUp = ip rule add not fwmark 51820 table 51820; ip rule add table main suppress_prefixlength 0; ip route del blackhole 0.0.0.0/0 || true; iptables -I OUTPUT -d 192.168.1.0/24 -j ACCEPT; ip route add 192.168.1.0/24 dev eth0 table main || true
PreDown = ip route del 192.168.1.0/24 dev eth0 table main || true; iptables -D OUTPUT -d 192.168.1.0/24 -j ACCEPT || true; ip rule del not fwmark 51820 table 51820; ip rule del table main suppress_prefixlength 0; ip route add blackhole 0.0.0.0/0 || true
Code language: JavaScript (javascript)
Replace 192.168.1.0/24 with your local subnet and eth0 with your network interface name.
The iptables -I OUTPUT -d 192.168.1.0/24 -j ACCEPT line inserts an ACCEPT rule at the top of the OUTPUT chain, so it is evaluated before the DROP rules from Step 4. When the VPN goes down, PreDown removes this exception and local traffic is blocked again.
Warning: Local network traffic is not encrypted by the VPN. Anyone on your LAN can see this traffic. Only enable this if you trust your local network.
Find your interface name and subnet:
ip addr show
Look for your active interface (commonly eth0, enp0s3, or wlp2s0) and its subnet.
9. Troubleshooting
No internet after wg-quick up:
- Check
wg show wg0for a recent handshake — if there is none, the tunnel is not established - Try pinging the server:
ping -c 1 203.0.113.10— if this times out, the server may still be reachable (many servers block ICMP ping while WireGuard UDP still works) - Check that
ip rule showcontains thefwmarkandsuppress_prefixlengthrules
Internet works after wg-quick down (kill switch not working):
- Check if the blackhole route is present:
ip route show | grep blackhole - If missing, add it:
sudo ip route add blackhole 0.0.0.0/0 - Ensure the
PreDownline inwg0.confincludesip route add blackhole 0.0.0.0/0
Locked out over SSH / lost all connectivity:
- From a local console (keyboard + monitor, or out-of-band management), remove the blackhole and flush the firewall rules:
sudo ip route del blackhole 0.0.0.0/0
sudo iptables -D OUTPUT ! -o wg0 -d 10.0.0.0/8 -j DROP
sudo iptables -D OUTPUT ! -o wg0 -d 172.16.0.0/12 -j DROP
sudo iptables -D OUTPUT ! -o wg0 -d 192.168.0.0/16 -j DROP
- This restores normal networking so you can reconnect via SSH and diagnose the issue
Cannot reach local devices:
- The default kill switch blocks all local traffic — see Section 8 for how to allow LAN access
- Verify your local subnet and interface name are correct
Summary
The kill switch prevents any IPv4 traffic from leaving your machine unencrypted. It works by:
- Using a blackhole route as the default when the VPN is down — all internet traffic is dropped
- Using
iptablesrules to block traffic to private networks (LAN) unless it exits through the VPN tunnel - Using
FwMarkand a custom routing table to allow only VPN-marked packets through when the tunnel is active - Using
PreDownto reinstate the blackhole before the tunnel goes down
Key commands:
sudo wg-quick up wg0— start the tunnel (removes blackhole, adds policy routes)sudo wg-quick down wg0— stop the tunnel (restores blackhole, removes policy routes)ip route show | grep blackhole— check if the kill switch is activecurl --max-time 5 ifconfig.me— test whether traffic leaks when the VPN is down