Your VPN Drops. Then What?
Most VPN tutorials stop at “here’s how to connect.” That’s like teaching someone to drive and ending the lesson before you mention the brakes.
When a VPN drops — and they drop, because networks are chaos — your OS doesn’t just sit there waiting. It falls back to the next available route: your regular internet connection. Your “private” traffic is now going out naked over your ISP’s network. This is called a VPN leak, and it happens silently, with no warning, often for minutes before you notice.
A kill switch prevents this. When the VPN goes down, traffic stops entirely rather than falling back. No fallback, no leak. Just silence until the VPN comes back.
Combined with DNS leak prevention — making sure your DNS queries also go through the VPN — you get something that’s actually worth calling a private connection.
WireGuard Kill Switch with iptables
WireGuard has PostUp and PreDown hooks that run shell commands when the interface comes up or goes down. This is where you put your kill switch logic.
Here’s a complete WireGuard config with a kill switch built in:
[Interface]PrivateKey = YOUR_PRIVATE_KEY_HEREAddress = 10.0.0.2/24DNS = 1.1.1.1
# Kill switch: block all traffic not going through WireGuardPostUp = iptables -I OUTPUT ! -o %i -m mark ! --mark $(wg show %i fwmark) -m addrtype ! --dst-type LOCAL -j REJECTPostUp = ip6tables -I OUTPUT ! -o %i -m mark ! --mark $(wg show %i fwmark) -m addrtype ! --dst-type LOCAL -j REJECT
# Tear down kill switch when VPN goes downPreDown = iptables -D OUTPUT ! -o %i -m mark ! --mark $(wg show %i fwmark) -m addrtype ! --dst-type LOCAL -j REJECTPreDown = ip6tables -D OUTPUT ! -o %i -m mark ! --mark $(wg show %i fwmark) -m addrtype ! --dst-type LOCAL -j REJECT
[Peer]PublicKey = SERVER_PUBLIC_KEYEndpoint = vpn.example.com:51820AllowedIPs = 0.0.0.0/0, ::/0PersistentKeepalive = 25Let’s unpack the iptables rule:
! -o %i— not going out through the WireGuard interface (%i= interface name, e.g.,wg0)! --mark $(wg show %i fwmark)— not marked by WireGuard (WireGuard marks its own traffic to bypass this rule)! --dst-type LOCAL— not going to localhost-j REJECT— drop everything else
When WireGuard is up, it marks its own packets with an fwmark so they bypass the OUTPUT rule. When WireGuard goes down, the iptables rule stays in the OUTPUT chain (PostUp ran, PreDown didn’t) — so all traffic is rejected. When PreDown runs cleanly (i.e., you intentionally bring the VPN down), it removes the rule, restoring normal traffic.
The AllowedIPs = 0.0.0.0/0 Pattern
The AllowedIPs = 0.0.0.0/0, ::/0 line is what makes this a full tunnel — all IPv4 and IPv6 traffic routes through the VPN peer.
Split tunnel alternative (if you only want specific traffic through the VPN):
# Only route private network traffic through VPNAllowedIPs = 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16
# Only route a specific external subnetAllowedIPs = 203.0.113.0/24Full tunnel vs split tunnel trade-offs:
| Full Tunnel | Split Tunnel | |
|---|---|---|
| Privacy | All traffic hidden from ISP | Only specific traffic hidden |
| Performance | VPN becomes bottleneck for everything | Local traffic unaffected |
| Kill switch | All-or-nothing | Harder to reason about leaks |
| DNS leaks | Easier to prevent | More complex configuration |
For privacy use cases: full tunnel. For performance-sensitive home lab access: split tunnel.
Testing Your Kill Switch
# Check your current IP before VPNcurl -s ifconfig.me
# Bring up WireGuardsudo wg-quick up wg0
# Verify IP changedcurl -s ifconfig.me
# Simulate VPN drop (bring down the interface without running PreDown)sudo ip link set wg0 down
# This should fail or hang — no traffic should leavecurl -s --max-time 5 ifconfig.me# Expected: curl: (28) Operation timed out
# Properly bring VPN down (runs PreDown, removes iptables rules)sudo wg-quick down wg0
# Traffic should work againcurl -s ifconfig.meThe key test is step 4: silently dropping the interface without running wg-quick down. Traffic should stop. If it doesn’t, your kill switch isn’t working.
What Is a DNS Leak?
DNS is the phone book: you ask “what’s the IP for google.com?” and a DNS server answers.
Your VPN encrypts your traffic, but DNS queries are just network traffic like anything else. Without explicit configuration, they might:
- Go out your regular network interface before the VPN routes kick in
- Use your ISP’s DNS servers instead of the VPN’s DNS
- Use systemd-resolved’s caching, which might have stale entries from before the VPN connected
The result: your VPN says you’re in Germany, but your DNS queries go through your US ISP’s servers. Any observer watching DNS traffic knows exactly what sites you’re visiting — and your ISP’s resolver can see it all.
Locking DNS Through the VPN
Method 1: The WireGuard DNS directive
The DNS = 1.1.1.1 line in your WireGuard config tells wg-quick to update your system’s DNS settings when the VPN connects. Simple, but depends on your system actually respecting it.
[Interface]# ...DNS = 1.1.1.1, 1.0.0.1# Or use your VPN provider's private DNSDNS = 10.0.0.1Method 2: systemd-resolved configuration
If you’re using systemd-resolved (most modern Ubuntu/Debian systems are), configure it to send DNS queries through the VPN interface:
# Check if systemd-resolved is runningsystemctl status systemd-resolved
# After WireGuard connects, tell systemd-resolved to use the VPN interface for DNSsudo resolvectl dns wg0 1.1.1.1sudo resolvectl domain wg0 "~." # The ~. means "all domains go through this interface"
# Verifyresolvectl statusFor a permanent fix, create a resolved configuration:
[Resolve]DNS=1.1.1.1 1.0.0.1DNSOverTLS=yesDNSSEC=yesMethod 3: Locking /etc/resolv.conf
The old-school approach — lock the resolv.conf file so nothing can change it:
# Set your DNS serverscat > /etc/resolv.conf << EOFnameserver 1.1.1.1nameserver 1.0.0.1EOF
# Make it immutable (not even root can change it without removing this flag)sudo chattr +i /etc/resolv.conf
# Verifylsattr /etc/resolv.conf# Should show: ----i---------e- /etc/resolv.confThis is blunt and breaks NetworkManager/DHCP trying to update DNS. Fine for a dedicated VPN device or server. Annoying on a laptop that moves between networks.
dnscrypt-proxy: Encrypting the DNS Queries Themselves
Even with DNS going through your VPN, your DNS queries land on a resolver somewhere. If that resolver is 1.1.1.1 at Cloudflare, you’re trusting Cloudflare. dnscrypt-proxy encrypts DNS traffic using the DNSCrypt protocol and lets you route to resolvers that don’t log.
apt install dnscrypt-proxy
# Edit /etc/dnscrypt-proxy/dnscrypt-proxy.tomllisten_addresses = ["127.0.0.1:5300"]
# Only use servers that don't log and aren't censoredrequire_nolog = truerequire_nofilter = truerequire_dnssec = true
# Fallback resolver (used only for initial bootstrap)fallback_resolvers = ["1.1.1.1:53", "8.8.8.8:53"]ignore_system_dns = truesystemctl enable dnscrypt-proxysystemctl start dnscrypt-proxy
# Update resolv.conf or systemd-resolved to use itecho "nameserver 127.0.0.1" > /etc/resolv.confThen in your WireGuard config:
DNS = 127.0.0.1This routes DNS through dnscrypt-proxy on localhost, which encrypts it and sends it through the VPN to a no-log resolver. It’s layered in a satisfying way.
Verifying DNS Isn’t Leaking
# Quick check — what DNS server is being used?dig +short whoami.akamai.net @ns1-1.akamaitech.net
# Or use a public DNS leak testcurl -s https://ipv4.icanhazip.com# Should show your VPN IP
# Check where DNS queries are goingdig example.com
# In the ANSWER section, look for the server used# Compare it against what you configuredFor a thorough test, use dnsleak.com or dnsleaktest.com from a browser — they run multiple DNS queries and show every resolver that responded.
Split Tunnel DNS (The Complicated Case)
If you’re running a split tunnel where only some traffic goes through the VPN, DNS gets complicated. You might want *.company.internal to resolve via the VPN’s DNS, and everything else via a public resolver.
With systemd-resolved:
# Route internal domain DNS through VPN interfacesudo resolvectl dns wg0 10.0.0.1sudo resolvectl domain wg0 "company.internal"
# Default DNS for everything elsesudo resolvectl dns eth0 1.1.1.1sudo resolvectl domain eth0 "~."The ~. on eth0 makes it the fallback for all domains not matched by another interface’s domain list.
The Practical Summary
For a “good enough for most threat models” WireGuard setup:
- Use
AllowedIPs = 0.0.0.0/0, ::/0for a full tunnel - Add the iptables kill switch rules to
PostUp/PreDown - Set
DNS = 1.1.1.1in your WireGuard config - Configure systemd-resolved to respect the VPN interface
- Test by simulating a drop with
ip link set wg0 down
If you want to go deeper: add dnscrypt-proxy for encrypted DNS, lock resolv.conf with chattr +i, and test with a proper DNS leak tester.
Paranoia is justified here. The whole point of a VPN is that your traffic doesn’t leak out the sides — and without these configurations, it absolutely does.