A VPN kill switch on Linux is a default-deny outbound firewall with three deliberate exceptions. It does not care which protocol you run, which is why one page covers OpenVPN and WireGuard together. It cares about one thing: whether a packet is leaving on the tunnel interface. This page gives the same kill switch three times, in UFW, iptables and nftables. It then covers the two things every competing page leaves out: a filtered IPv6 rule, and a way to get your network back when the rules will not lift.
What a kill switch on Linux has to do
The model is the same regardless of which tool writes it.
- Set the default outbound policy to deny, so anything not explicitly allowed is dropped
- Allow outbound traffic on the VPN interface, usually
wg0ortun0 - Allow the VPN server address, port and protocol, so the tunnel can establish through the block
- Allow the loopback interface and, if you need it, one named LAN subnet
Everything else is refinement. If you can name why each allow rule exists, the kill switch is correct. If an allow rule is there because a guide said so, remove it and see what breaks.
Read the warning before running anything. These commands cut outbound network access. Run them on a machine you have console access to, not over SSH. The session that would let you fix a mistake is the session the rules drop. Order matters too. Add the allow rules first and set the deny policy last, so you never sit in a fully blocked state.
Identify your VPN interface
Identify your VPN interface before writing any rule. Run ip -brief link show and read the list. WireGuard interfaces are usually wg0, and OpenVPN interfaces are usually tun0. Both names are conventions rather than guarantees, and a second tunnel or a manually named interface will change them.
Bring the tunnel up first, then run the command, then bring it down again. The interface only exists while the tunnel is running, and a rule written against a name that does not exist yet is a rule that silently matches nothing. Note the name exactly, including the digit.
Read the server address and port from the config file you already have. In a WireGuard .conf it is the Endpoint line. In a .ovpn profile it is the remote line, with the protocol on proto. Resolve a hostname to an address before you use it in a rule, because DNS will not be available once the block is active.
That last step has a consequence worth planning for. A rule pinned to one address stops matching the moment the provider moves the endpoint, and the failure is silent. The tunnel simply never establishes, which looks exactly like a blocked network. Re-resolve the hostname and compare it against your rule before you debug anything else. If your provider rotates addresses often, allow the process instead of the address: match on the openvpn binary with an owner or cgroup rule rather than on a destination that can change under you.
If you have to do this on a remote machine
Console access is the right answer and it is not always available. When the machine is only reachable over SSH, give yourself an automatic way back before you type the rule that could remove it.
sudo systemd-run --on-active=180 --unit=killswitch-rollback /usr/sbin/ufw disableThat schedules the undo in three minutes. Build the rules, confirm your session still works, confirm the tunnel still establishes, then cancel the rollback with sudo systemctl stop killswitch-rollback.timer. If anything goes wrong, do nothing and wait: the timer restores access without you. Substitute the iptables or nftables teardown from the last section on this page if you did not use UFW.
Add an exception for your own management path as well, and note that the three tools differ on how much you have to write.
sudo ufw allow in from 198.51.100.25 to any port 22 proto tcp
sudo iptables -A INPUT -s 198.51.100.25 -p tcp --dport 22 -j ACCEPT
sudo iptables -A OUTPUT -d 198.51.100.25 -p tcp --sport 22 -j ACCEPTReplace 198.51.100.25 with the address you administer the machine from. UFW needs only the inbound rule, because its shipped before.rules already accepts established and related traffic in both directions. The raw iptables ruleset on this page has no such rule. There the outbound half is what carries your SSH replies, and leaving it out drops the session the moment the policy changes. A kill switch that blocks your own management path is a kill switch you cannot maintain.
Three ways to build a VPN kill switch on Linux
Pick one. Running two of these at once produces a state that is very hard to reason about. UFW writes iptables rules, and iptables may itself be an nft compatibility layer on your distribution.
UFW
UFW is the accessible route and it is the one the existing kill switch setup guide describes. Replace wg0, the address and the port with your own values.
sudo ufw allow out on wg0 from any to any
sudo ufw allow in on wg0 from any to any
sudo ufw allow out to 203.0.113.10 port 51820 proto udp
sudo ufw default deny outgoing
sudo ufw default deny incoming
sudo ufw enableCheck the result with sudo ufw status verbose. The header lines should read deny (incoming) and deny (outgoing), and your three allow rules should be listed below.
For OpenVPN the only change is the interface and the endpoint line. Use tun0 and proto udp on port 1194, or proto tcp on port 443 if your profile connects that way. Read using an OpenVPN profile if you are not sure which one your profile uses.
iptables
The portable route. Every allow rule comes first, and the policy change comes last.
sudo iptables -A OUTPUT -o lo -j ACCEPT
sudo iptables -A INPUT -i lo -j ACCEPT
sudo iptables -A OUTPUT -o wg0 -j ACCEPT
sudo iptables -A INPUT -i wg0 -j ACCEPT
sudo iptables -A OUTPUT -d 203.0.113.10 -p udp --dport 51820 -j ACCEPT
sudo iptables -A INPUT -s 203.0.113.10 -p udp --sport 51820 -j ACCEPT
sudo iptables -P OUTPUT DROP
sudo iptables -P INPUT DROP
sudo iptables -P FORWARD DROPVerify with sudo iptables -S, which prints the ruleset in the same syntax you typed. The three -P lines are the kill switch. Everything above them is the exception list.
These rules do not survive a reboot on their own. Debian and Ubuntu package iptables-persistent, which Debian describes as a boot-time loader for netfilter rules. With it installed, sudo netfilter-persistent save calls each plugin with the save argument, writing the currently loaded rules to persistent storage. On any other distribution, find out what restores rules at boot before you assume this ruleset survives one.
nftables
The modern route, and the one no page on this SERP covers. Write the whole ruleset to a file rather than issuing commands, because a file is something you can read, diff and revert.
#!/usr/sbin/nft -f
flush ruleset
table inet killswitch {
chain input {
type filter hook input priority 0; policy drop;
iif "lo" accept
iifname "wg0" accept
ip saddr 203.0.113.10 udp sport 51820 accept
}
chain output {
type filter hook output priority 0; policy drop;
oif "lo" accept
oifname "wg0" accept
ip daddr 203.0.113.10 udp dport 51820 accept
}
chain forward {
type filter hook forward priority 0; policy drop;
}
}Save it as /etc/nftables.conf, check the syntax with sudo nft -c -f /etc/nftables.conf, then load it with sudo nft -f /etc/nftables.conf. Enable it at boot with sudo systemctl enable --now nftables. The -c check is worth the extra command, because a file that fails to parse halfway through leaves you with a partial ruleset.
flush ruleset at the top is deliberate and it is also destructive. It removes every existing rule on the machine, including anything Docker, libvirt or Podman has installed. Those services rewrite their rules on restart, so restart them afterwards or leave the flush line out and accept that reloading will stack duplicate tables. Whichever of the three rulesets you choose, load it once with a rollback scheduled, as in the section above, before you enable it at boot. A ruleset you have never watched load is not a kill switch yet.
IPv6
An IPv4 rule filters IPv4. Leave it there and every dual-stack destination stays reachable over IPv6 the moment the tunnel drops. That is not a partial failure of the kill switch but a complete one.
Each tool handles this differently, and only one of them handles it for you.
sudo ip6tables -A OUTPUT -o lo -j ACCEPT
sudo ip6tables -A INPUT -i lo -j ACCEPT
sudo ip6tables -A OUTPUT -o wg0 -j ACCEPT
sudo ip6tables -A INPUT -i wg0 -j ACCEPT
sudo ip6tables -P OUTPUT DROP
sudo ip6tables -P INPUT DROP
sudo ip6tables -P FORWARD DROPUFW applies its default policies to IPv6 as well. Its manual page states that IPv6 is allowed by default, and that setting IPV6 to no in /etc/default/ufw and reloading restricts IPv6 to the loopback interface. Read that file rather than assume nobody has changed it on this machine.
The nftables ruleset above already covers IPv6, because an inet table filters both families and the policy drop applies to both. The ip daddr line matches IPv4 only, so an IPv6 endpoint needs a matching ip6 daddr line. That asymmetry catches people: the block is dual-stack by default, the exception is not.
Disabling IPv6 entirely at the sysctl level is the answer several competing pages give. It works, and it is a blunt instrument that also breaks IPv6 inside the tunnel. Filter it instead.
LAN exceptions
The rules above block your printer, your NAS and your router admin page along with everything else. Nothing leaks as a result, so this is a usability decision rather than a security one. Make it knowingly.
sudo ufw allow out to 192.168.1.0/24
sudo ufw allow in from 192.168.1.0/24
sudo iptables -A OUTPUT -d 192.168.1.0/24 -j ACCEPT
sudo iptables -A INPUT -s 192.168.1.0/24 -j ACCEPTIn nftables, add ip daddr 192.168.1.0/24 accept to the output chain and the matching ip saddr line to the input chain.
Scope the exception to the subnet your router actually hands out. Read it with ip -brief addr show. Allowing 192.168.0.0/16, 10.0.0.0/8 and 172.16.0.0/12 together is the common shortcut, and it opens every private range on every network you ever join, including hotel wifi. One /24 is almost always the right answer.
Choosing between the three
| UFW | iptables | nftables | |
|---|---|---|---|
| Distribution default | Installed by default on Ubuntu, available everywhere | Present or emulated on effectively every distribution | Often the backend already in use. Run iptables -V to see which one yours names |
| Persistence across reboot | Built in. Rules survive once ufw enable has run | None by default. Needs iptables-persistent or a save file | Built in through /etc/nftables.conf and the nftables service |
| IPv6 handling | Automatic when IPV6=yes is set | A second, fully mirrored ip6tables ruleset | Automatic in an inet table, except for address-matching exceptions |
| Ease of teardown | sudo ufw disable restores connectivity in one command | Reset three policies, then flush. Four commands minimum | sudo nft flush ruleset in one command, at the cost of every other rule |
The practical read is short. Choose UFW to have this working in five minutes. Choose nftables for a ruleset you can still read in six months. Choose iptables when you are scripting against distributions you do not control.
When the kill switch stays closed
This is the state that generates the most searches and the least documentation. The tunnel is down, the VPN client is closed, possibly uninstalled, and the machine still has no internet. Nothing is broken. Firewall rules are operating system state and they have no idea the VPN ever existed.
Restore connectivity first and investigate afterwards.
sudo ufw disable
sudo iptables -P INPUT ACCEPT
sudo iptables -P OUTPUT ACCEPT
sudo iptables -P FORWARD ACCEPT
sudo iptables -F
sudo ip6tables -P INPUT ACCEPT
sudo ip6tables -P OUTPUT ACCEPT
sudo ip6tables -P FORWARD ACCEPT
sudo ip6tables -F
sudo nft flush rulesetReset the policies before you flush the chains. Flushing first removes your allow rules while leaving the DROP policy in place, which is the same locked state with fewer clues. Confirm the teardown worked before you move on. Ping an address outside your own subnet, then resolve a name, because a restored policy and a working resolver are not the same thing.
Then deal with the saved copy, or the rules come back at the next boot. Check /etc/iptables/rules.v4, /etc/nftables.conf and whether the nftables or netfilter-persistent service is enabled. A runtime flush and a disk file disagree quietly, and the disk file wins on reboot.
If a stranded rule came from a WireGuard PostUp hook rather than from this page, the fix is the same but the cause is different. That case is covered on the WireGuard kill switch page, which handles the protocol-level version of this setup.
Confirming it blocks
Connect, confirm browsing works, disconnect, confirm traffic stops. That is a sanity check and most pages stop there.
It proves less than it appears to. A browser tab tests one application in the foreground, and it tests it after the risky interval has already passed. Name resolution and raw address traffic fail differently and need separate assertions. Run a packet capture on the physical interface, exercise both, then drop the tunnel several different ways rather than one. The full method is in how to test if a VPN kill switch works.
Quick answers
- Why is my internet still blocked after I disconnected the VPN
- Because the firewall rules are permanent and the tunnel is not. The default deny policy stays in place whether the VPN client is running, stopped or removed from the system. Nothing clears it automatically. Run sudo ufw disable, or reset the iptables policies to ACCEPT, or flush the nftables ruleset. Then check whether a saved ruleset will reinstate the block at the next boot.
- Do these rules survive a reboot
- UFW and nftables do, by design. Raw iptables rules do not, and are lost at shutdown unless you save them with netfilter-persistent or write them to the file your distribution restores from. That difference catches people both ways. Some find the kill switch gone after a reboot, and others find one they cannot remove because a saved file keeps restoring it.
- Does vpn affect local network
- Yes, once a kill switch is running. A default deny outbound policy blocks your LAN alongside the internet, so printers, network shares and the router admin page all stop responding. Add one explicitly scoped subnet exception to restore them. Without a kill switch, a full-tunnel VPN can also route LAN traffic into the tunnel, which is a routing question rather than a firewall one.
- Will this work with both OpenVPN and WireGuard
- Yes. Every rule on this page matches on the interface name, the server address and the port, and none of them knows or cares which protocol produced the interface. Change wg0 to tun0, change the port and protocol to match the remote and proto lines in your .ovpn file, and the rest is identical. That is the advantage of building at the firewall rather than inside the client.
- How to disable vpn kill switch
- Run sudo ufw disable if you built it with UFW. For iptables, set the INPUT, OUTPUT and FORWARD policies back to ACCEPT before flushing the chains, and repeat for ip6tables. For nftables, run sudo nft flush ruleset. Then remove or disable whatever restores the rules at boot, or the block returns on the next start. For where a kill switch fits across operating systems, start at the kill switch setup guide. The client-side steps are in installing WireGuard and using an OpenVPN profile. Keep what a kill switch does and does not do nearby, because a kill switch does not fix bad DNS settings by itself. If the tunnel is not coming up at all, that is connection troubleshooting rather than a firewall problem.