VPN Killswitch for Kali Linux — 7 Easy Steps 🔒
How to Setup WireGuard ProtonVPN on Kali Linux (Step-by-Step Guide) 🧭: Part 2/4
Ethical Hacking Series 1 – Part 2 This is the second part of my Ethical Hacking Series on VPN setup in Kali Linux. View all 4 parts →
When I first tested a VPN Killswitch for Kali Linux with ProtonVPN WireGuard, I learned some hard lessons. Building a fail-closed VPN on Kali is very different from running the default app — here the goal is to create a Linux VPN firewall that enforces policy. In practice, this means turning a simple tunnel into a secure tunnel lock that refuses to leak even when things go wrong. The first time I brought ProtonVPN WireGuard up on Kali Linux, I celebrated too early. A brief drop later and traffic happily flowed over my default interface. That was the moment I accepted a hard truth: a tunnel without a fail‑closed policy is a nice‑to‑have, not a safety net.
I didn’t want a black‑box firewall or a 200‑line ruleset I’d forget in a week. I wanted something I fully understood and could recover from quickly if I messed it up. Over a dozen VM snapshots and a few self‑inflicted lockouts later, I settled on one approach that keeps winning: nftables with a tiny, auditable ruleset; scripts I can run from dock buttons; a strict DNS path; and a panic switch that cuts all packets on hostile Wi‑Fi before I even try to connect.
This is the sequel to my baseline guide where I set up the provider’s WG profiles on Kali. Here, we go from “works” to “works safely” — meaning fail‑closed by default, repeatable verification, and fewer surprises when you update Kali or roam across networks.
Did you read already my first post in this series How to setup WireGuard ProtonVPN on Kali Linux?
Promise of this guide: if the tunnel drops, your machine should go dark. No trickle of DNS, no sneaky fallback to your ISP route. If it’s not through wg0, it doesn’t leave.
Key Takeaways 🧭
These highlights explain why building a VPN Killswitch for Kali Linux is essential for a secure workflow.
- Fail-closed by default: with this nftables ruleset, no tunnel = no traffic. Outbound is dropped unless it’s via wg0 or the single handshake you allow.
- Minimal & auditable: ~15 lines you can read in a minute; no black-box magic.
- One-click control: dock buttons for Kill-On/Off, Panic-On/Off, and Region switch keep your workflow fast and predictable.
- DNS path locked: DNS queries are forced through the secure tunnel; browser DoH is tested and re-enabled inside the tunnel only.
- No accidental double tunnels: the switcher brings one region up at a time and tears the rest down.
- 5-minute verification: a repeatable routine (IP, handshake, DNS, block-test) catches 95% of issues before they bite.
- Extensible: add endpoint-pinned rules, LAN exceptions via policy routing, MTU tuning, and per-app isolation when needed.
What you’ll build (exactly) 🎯
- A minimal nftables VPN killswitch that only permits packets via wg0 plus the handshake to your chosen endpoint.
- On/Off scripts and a panic button that blocks everything instantly.
- A DNS leak guard that forces resolvers through the secure tunnel (and won’t let apps wander off).
- Dock shortcuts (.desktop files) for one‑click actions you’ll actually use.
- Multi‑region switching (US/EU/IN in my lab) that avoids double tunnels and weird routes.
- A 5‑minute verification routine you can run after updates, on new networks, or before a trip.
- A large, practical troubleshooting appendix that reflects common breakages I’ve hit (and fixed).


What I’m using (and why) 🧰 for my VPN killswitch for Kali Linux
Kali Linux in a VM (VirtualBox/VMware). NAT or bridged both work; bridged is handy for some lab cases.
A plan with downloadable WireGuard profiles from the provider. I keep three: protonus, protonbe (or protonnl), and protonin.
Packages:
sudo apt update
sudo apt install -y nftables wireguard-tools resolvconf dnsutils curl jqIf you prefer systemd‑resolved over resolvconf, that’s fine. The point is: wg‑quick must be able to swap resolvers on up/down.
Why a VM and not a USB stick?
Reliability and speed. Updating a VM is predictable, snapshots are instant, and dock integrations don’t get wiped by portable quirks. If I lock myself out (happens), I roll back a snapshot and try again.
Step 1 — Start clean 🧹
Begin with a blank firewall so old rules don’t haunt you:
sudo nft flush ruleset
sudo nft list ruleset # should print nothingTake a VM snapshot now if you like having a “save point.” I do — especially before firewall experiments.
Step 2 — The minimal nftables killswitch 🛡️
Save this as ~/vpn/killswitch.nft:
flush ruleset
table inet filter {
chain input {
type filter hook input priority 0;
policy drop;
iif lo accept
ct state established,related accept
iifname wg0 accept
}
chain forward {
type filter hook forward priority 0;
policy drop;
}
chain output {
type filter hook output priority 0;
policy drop;
oif lo accept
ct state established,related accept
oifname wg0 accept
udp dport 51820 accept # WG handshake; tighten to endpoint IP:port below
}
}
What it means (no magic):
INPUT → allow lo, established connections, and traffic coming in on wg0; drop the rest.
FORWARD → nothing forwarded (fine for a VM).
OUTPUT → default drop; allow lo, established, out via wg0, plus the UDP handshake so the tunnel can come up.
Hardening variants:
Lock the handshake to your exact endpoint IP and port (preferred):
add rule inet filter output ip daddr 185.159.X.X udp dport 51820 accept
If your endpoint is a hostname, resolve it before connecting and update this allow rule when it changes. You can even cron a small resolver check that rewrites the rule if the endpoint IP rotates.
If your provider uses non‑default ports, change udp dport 51820 accordingly.
IPv6 note: If your exit doesn’t do v6, don’t force it. Keep ::/0 out of AllowedIPs for now. Add it back when you have a v6‑capable region and you’ve tested it.


Step 3 — Scripts that save hours ⚡
I keep scripts in ~/vpn/scripts/ so I can bind them to dock buttons and muscle memory.
kill-onEnforce the ruleset:
#!/usr/bin/env bash
set -euo pipefail
sudo nft -f ~/vpn/killswitch.nft
echo "[kill-on] nftables killswitch active."
kill-offFlush everything (use with care):
#!/usr/bin/env bash
set -euo pipefail
sudo nft flush ruleset
echo "[kill-off] nftables rules flushed."Make them executable:
chmod +x ~/vpn/scripts/kill-*Persistence (only when you’re done tuning):
sudo systemctl enable nftables
sudo sh -c 'nft list ruleset > /etc/nftables.conf'During tuning I don’t persist. Once stable, I export to /etc/nftables.conf so it survives reboots.
Optional: apply on boot, but only after network is up. You can wrap kill-on in a simple systemd unit that After=network-online.target — but with a VM, I usually trigger it manually from a dock button so I see the state.
Step 4 — Panic button 🔴 (instant blackout)
Sometimes you don’t want “only wg0,” you want no packets whatsoever. Create ~/vpn/panic.nft:
flush ruleset
table inet filter {
chain input { type filter hook input priority 0; policy drop; }
chain forward { type filter hook forward priority 0; policy drop; }
chain output { type filter hook output priority 0; policy drop; }
}Scripts:
# panic-on
#!/usr/bin/env bash
set -euo pipefail
sudo nft -f ~/vpn/panic.nft
echo "[panic-on] ALL traffic blocked."# panic-off
#!/usr/bin/env bash
set -euo pipefail
sudo nft flush ruleset
echo "[panic-off] Traffic unblocked (no firewall rules)."Quick proof:
./panic-on
ping -c 1 1.1.1.1 # should fail instantly
./panic-off
I use panic before connecting on sketchy hotel Wi‑Fi and during captive portal dances (sign in with panic off, then immediately re‑enable the rules or bring up the tunnel).


Step 5 — DNS leak guard (strict, but simple) 🌐🔍
DNS is the most common “silent failure.” My guard is layered:
A) Ensure a DNS manager is running (so wg-quick can swap resolvers):
resolvectl status 2>/dev/null || systemctl status systemd-resolved 2>/dev/null || trueB) Pin the in‑tunnel resolver in your profile (under [Interface]):
DNS = 10.2.0.1C) Enforce via nftables (deny queries that don’t go to the in‑tunnel resolver):
add rule inet filter output ip daddr != 10.2.0.1 udp dport 53 drop
add rule inet filter output ip6 daddr != fd00:10:2::1 udp dport 53 dropD) Verify resolvers and path
resolvectl dns 2>/dev/null || cat /etc/resolv.conf
# who am I hitting?
dig +short TXT whoami.cloudflare @1.1.1.1If you still see your ISP’s resolver, reload the rules, bounce the tunnel, and temporarily disable browser DoH/DoT while testing (it can bypass OS DNS). Afterward you can re‑enable DoH inside the secure tunnel if you like.
“A VPN without a leak policy is a false sense of security. Always test DNS, IPv6, and WebRTC after setup.”
Why not force DoH at the OS layer? You can, but it complicates captive portals and early‑boot scenarios. I keep OS DNS simple, and if I want DoH, I run it over the tunnel, not around it.
Step 6 — One‑click dock buttons 🖱️✨
Typing commands invites mistakes. I use .desktop files that point to my scripts. Example:
[Desktop Entry]
Name=VPN Kill‑On
Exec=/home/robin/vpn/scripts/kill-on
Icon=security-high
Type=ApplicationRepeat for Kill‑Off, Panic‑On, and Panic‑Off. Now it’s literally one click.
Multi‑region switching 🌍
I keep region names short and predictable (US, BE/NL, IN). Script ~/vpn/scripts/switch-region:
#!/usr/bin/env bash
set -euo pipefail
REG=${1:-us} # us|be|nl|in
sudo wg-quick down protonus 2>/dev/null || true
sudo wg-quick down protonbe 2>/dev/null || true
sudo wg-quick down protonnl 2>/dev/null || true
sudo wg-quick down protonin 2>/dev/null || true
sudo wg-quick up proton${REG}
echo "[switch-region] active: proton${REG}"If a region is congested or flaky, I hop to another. If a connection fails, the killswitch keeps me dark until the next one succeeds.
Advanced hardening: If you pinned the handshake rule to a specific endpoint IP:port, add per‑region allows during switch (remove the previous one first) to keep your ruleset tight.
Step 7 — My 5‑minute verification routine ✅🧪
This rhythm catches 95% of issues before they matter:
# 1) Enforce fail‑closed
~/vpn/scripts/kill-on
# 2) Bring up a region
sudo wg-quick up protonus
# 3) Handshake visible?
wg
# 4) Public IP is the provider, not ISP?
curl -s ifconfig.io; echo
# 5) DNS sanity
dig +short TXT whoami.cloudflare @1.1.1.1
# 6) Bring it down
sudo wg-quick down protonus
# 7) Still blocked?
ping -c 1 1.1.1.1 # must FAIL
Bonus checks:
sudo nft list ruleset | sed -n '1,160p' # spot that OUTPUT policy is drop
wg show interfaces
journalctl -u wg-quick@protonus --no-pager -e
If ping doesn’t fail with the tunnel down, reload the killswitch: nft -f ~/vpn/killswitch.nft and re‑check.


Explore more about ethical hacking
Why a VPN Killswitch for Kali Linux with nftables (not iptables/UFW)? 🔧 (not iptables/UFW)? 🔧
I started with UFW because it’s beginner‑friendly. But once I wanted precision — allow only wg0 traffic + a single UDP port — it got clunky. iptables works, but nftables is the modern default on Linux. Benefits I actually feel:
- Cleaner syntax I’ll remember in 6 months.
- Unified IPv4/IPv6 handling (fewer blind spots).
- Fast path evaluation.
- Plays natively with WG.
“nftables is the future of Linux firewalls. Cleaner syntax, faster evaluation, and native IPv6 support.”
Performance & clarity extras ⚙️📈
- Logs oneliner: journalctl -u wg-quick@protonus –no-pager -e for tunnel events.
- nft live view: sudo nft monitor trace when debugging packet flow (chatty but educational).
- Endpoint latency: ping -c 3 ENDPOINT_IP before connecting; a bad path can masquerade as “WG issues.”
- CLI niceties: aliases like myip=’curl -s ifconfig.io’, wgs=’wg show’ speed up checks.
- MTU sanity: slow loads/timeouts? Try MTU = 1380 in [Interface], then tune ±20.
- Keep notes: one line per session (region, IP, resolver, oddities). That habit pays back every trip.
Mistakes I actually made (so you don’t have to) 🚨
- Forgot the handshake allow → WG never came up. Fix: allow UDP 51820 or, better, allow only the endpoint IP:port.
- Confused profile names → dock buttons did nothing. Use short, stable names (protonus, protonbe, protonin).
- Flushed rules without a plan → locked myself out. Keep kill-off and panic-off handy.
- Assumed DNS “just works” → leaked until I enforced rules and verified.
- Two tunnels at once → bizarre routes. One region at a time.
- IPv6 grief → remove ::/0 in AllowedIPs if the exit lacks v6; re‑enable later.
- Browser DoH confusion → disable DoH for testing, then run it inside the secure tunnel.
- Endpoint by hostname + strict rules → handshake blocked before DNS; temporarily relax or pin the IP.


Extended troubleshooting (field‑tested) 🔎🔧
- 1. Tunnel up, no internet → Check ip route and AllowedIPs=0.0.0.0/0 (and ::/0 only if you truly want v6).
- 2. Handshake fails → Endpoint blocked. Temporarily allow udp dport 51820; then tighten to ip daddr ENDPOINT_IP udp dport ENDPOINT_PORT accept.
- 3. DNS leak → Ensure resolvconf/systemd-resolved is running; set DNS = 10.2.0.1; enforce via nftables.
- 4. Slow DNS → Consider DoH inside the tunnel or different in‑tunnel resolvers from the provider.
- 5. MTU pain → Add MTU = 1380 (tune ±20). Symptom is partial page loads/timeouts.
- 6. LAN unreachable (NAS/printers) → Policy routing: route your local subnet via main and let the internet use wg0.
- 7. VM lost networking after updates → Verify adapter mode (NAT/Bridged) didn’t change; re‑enable nftables service.
- 8. Firewall typos → sudo nft list ruleset and check each hook/policy. One missing semicolon can ruin your day.
- 9. Dock button does nothing → Check executable bit (chmod +x) and the Exec= path.
- 10. Multiple tunnels → wg show interfaces; bring all down, start one.
- 11. P2P blocked → Use a P2P‑friendly region from the provider.
- 12. WebRTC leak → In Firefox: about:config → media.peerconnection.enabled=false (or use a privacy extension).
- 13. Panic not absolute → Make sure UFW/iptables aren’t running in parallel; stick to nftables only.
- 14. Persistence surprise → You thought rules were persisted; /etc/nftables.conf wasn’t updated. Export again after changes.
- 15. Endpoint is a hostname → DNS failure can break handshakes under strict rules. Prefer IP in Endpoint=.
- 16. Per‑app split tunneling → On Linux: use policy routing or network namespaces/containers; GUI split is easier in official apps.
- 17. Kali update broke resolv → Restart systemd-resolved or reinstall resolvconf; bounce tunnel, re‑test.
- 18. Random drops → Add PersistentKeepalive = 25 under [Peer].
- 19. IPv6 resolvers unclear → Test curl -6 ifconfig.io; if v6 is flaky, run v4‑only until you have a v6‑capable exit.
- 20. No rollback → Always keep kill-off/panic-off scripts and consider VM snapshots before big changes.


- 21. Handshake repeats every 2s → MTU/path MTU discovery problems; try MTU=1380.
- 22. Only some sites fail → Likely DNS or MSS/MTU; test direct IP curl http://1.1.1.1.
- 23. wg shows rx/tx = 0 → Handshake OK but routing wrong; verify default route via wg0.
- 24. NetworkManager interference → Disable per‑connection DNS overrides, or manage routes manually.
- 25. Can’t resolve endpoint hostname (strict rules) → Temporarily allow port 53 to your chosen resolver until wg0 is up, then re‑enforce.
- 26. Docker containers bypassing rules → Bind containers to a netns that obeys nftables or route them via wg0 explicitly.
- 27. Flatpak app ignoring system proxy → Some apps do their own DNS; enforce at firewall‑level and test with packet traces.
- 28. IPv6 leak via SLAAC → Disable v6 temporarily (sysctl -w net.ipv6.conf.all.disable_ipv6=1) while testing.
- 29. Time skew blocks handshake → Sync time: sudo timedatectl set-ntp true.
- 30. Kernel upgrade changed interface name → Confirm it’s still wg0; adapt rules if you renamed it.
- 31. VPN connects but speed is low → Switch region, check exit load, or try different offload settings (kernel/WG updates can help).
- 32. DNS over HTTPS inside tunnel breaks captive portal login → Use panic or kill‑off to sign in, then re‑enable rules.
- 33. UFW and nftables both active → Pick one; mixed stacks cause surprises.
- 34. Endpoint IP changed → If you pinned IP:port in rules, update it; consider a lightweight cron to refresh.
- 35. IPv6‑only network → Ensure your exit supports v6 or force v4‑only until it does.
- 36. Resolv.conf keeps reverting → Something else manages DNS; align NetworkManager/systemd‑resolved/resolvconf roles.
- 37. WireGuard shows handshakes but no DNS → Your firewall DNS rules are too strict or DNS= missing; re‑apply Step 5.
- 38. Web apps work, CLI tools don’t → Proxy/env vars mismatch; verify both OS‑level DNS and routes.
- 39. Packet loss spikes on Wi‑Fi → Test on Ethernet to rule out RF issues; don’t blame WG prematurely.
- 40. Audit anxiety → Export nft list ruleset to a file and annotate each line. Future‑you will thank you.


Frequently Asked Questions ❓
❓ Do I need a plan that supports third‑party configs?
Yes — paid tiers provide downloadable WG .conf files for Linux.
❓ Why a nftables VPN killswitch instead of UFW?
It’s lean, modern, IPv4/IPv6 unified, and easy to audit.
❓ How do I test for DNS leaks with Proton on Kali?
Run dig +short TXT whoami.cloudflare @1.1.1.1 and a browser leak test; resolvers should belong to the provider.
❓ Panic vs killswitch — what’s the difference?
Killswitch permits only wg0 + handshake; panic blocks everything, including the tunnel.
❓ Can I add multi‑region profiles without leaking?
Yes — keep one region up at a time and let the killswitch enforce fail‑closed behavior.
❓ Will a killswitch break my LAN printer or NAS?
It can. Add policy routes for local subnets so LAN stays reachable while internet uses the secure tunnel.
❓ What’s the best no‑GUI verification flow?
wg, curl ifconfig.io, dig whoami.cloudflare, nft list ruleset.
❓ How to block traffic if the VPN disconnects on Linux (quick test)?
Turn the killswitch on, bring the tunnel down, confirm ping 1.1.1.1 fails.
❓ Is WireGuard safer than OpenVPN in this setup?
WG is faster and simpler; security comes from correct routing, DNS policy, and fail‑closed firewall rules.
❓ What MTU should I try if browsing is slow?
Start at MTU=1380 and adjust ±20.
❓ What breaks most often after Kali updates?
nftables persistence and VM adapter mode. Re‑enable, re‑test, and re‑export /etc/nftables.conf if needed.
❓ How can I allow only my exact endpoint?
Lock the output rule to ip daddr ENDPOINT_IP udp dport ENDPOINT_PORT accept instead of any 51820.
❓ Can I use DoH with this setup?
Yes — but run DoH inside the tunnel. Disable it temporarily when testing OS‑level DNS.
❓ How do I keep notes that actually help later?
Log one line per session: region, IP, resolver, any odd behavior.
❓ Can I keep LAN access while blocking the internet without WG?
Yes — add local subnet exceptions with policy routing, while the default route remains blocked.
❓ Why choose a VM for this workflow?
Snapshots, isolation, and predictable networking make repetitive testing safer and faster.
❓ OpenVPN vs WireGuard — which should I pick?
For my workflow: WG for speed and simplicity. If you need strict legacy compatibility or enterprise PKI quirks, OpenVPN can still be handy.
❓ What if my provider rotates endpoint IPs?
Resolve the hostname before connecting and update the pinned IP in your rule, or allow the handshake port temporarily while connecting.
❓ How do I allow LAN printers while staying fail‑closed for internet?
Add explicit routes for 192.168.x.0/24 (or your LAN) via the main table; keep default routes on wg0.
Closing thoughts on the VPN Killswitch for Kali Linux 🧠
This setup is my answer to: “How do I stop leaks if my WG tunnel drops on Kali?” It’s auditable, reproducible, and fast to use. With dock buttons, a panic switch, a strict DNS guard, and clean region switching, I finally have a workflow I trust on real‑world networks — cafés, hotels, airports, and lab rigs.
In Part 2 of this series, you’ll secure your VPN with a custom kill switch on Kali Linux. Explore the other guides to complete your full VPN workflow:
- Part 2: VPN Kill Switch for Kali Linux
- Part 3: Kali Linux VPN Automation
- Part 4: Kali Linux Split Tunneling
👉 This step ensures your traffic never leaks outside the VPN tunnel.
Next up: I’m building the ultimate dock menu for Kali — imagine clicking once to switch regions, kill traffic, or trigger panic mode. No more typing, just speed and safety.
See my post on Kali Linux vpn automation.
Stay sharp, stay invisible 👻

