Pop Art grid of colorful shields with padlocks, symbolizing cybersecurity and protection.

VPN Killswitch for Kali Linux — 7 Easy Steps 🔒

I learned this the annoying way: a VPN tunnel can drop for one second, and Linux will happily continue routing traffic like nothing happened. Your browser won’t scream. Your terminal won’t warn you. Your ISP just gets a little surprise cameo.

This guide is my “no excuses” approach: a small nftables ruleset that enforces fail-closed behavior, simple scripts you can bind to dock buttons, and a verification routine that catches problems before you trust the connection.

VPN Killswitch for Kali Linux should mean one thing: if the tunnel isn’t up, nothing leaves. Not DNS. Not “just one packet.” Not “oops I clicked refresh.”

This post builds on my baseline WireGuard setup guide. If you haven’t done that yet, start here first:

👉 How to Setup WireGuard ProtonVPN on Kali Linux (Step-by-Step Guide) 🧭

Promise of this guide: if the tunnel drops, your machine should go dark. No DNS trickle. No fallback. If it’s not through wg0, it doesn’t leave.

Key Takeaways 🧭

What you’ll get after following this guide:

  • Default-deny outbound rules so “tunnel down” automatically means “no traffic.”
  • A tiny firewall policy you can actually read and audit in under a minute.
  • One-click scripts for enabling/disabling the policy and for instant blackout mode.
  • A strict DNS path so your resolver doesn’t quietly wander off.
  • A repeatable 5-minute verification routine that prevents “I assumed it was fine.”
  • Clean region switching without weird routing or accidental double tunnels.

What you’ll build (exactly) 🎯

  • A minimal nftables killswitch that only allows traffic via wg0 (plus what’s needed to establish the tunnel).
  • A “panic button” ruleset that blocks everything instantly.
  • A DNS guard that refuses off-tunnel resolver traffic.
  • Scripts you can attach to dock buttons or desktop launchers.
  • Multi-region switching without overlap or leaks.
VPN Killswitch for Kali Linux

What I’m using (and why) 🧰

Environment: Kali Linux in a VM (VirtualBox/VMware). NAT or bridged both work. Bridged can be handy for some lab scenarios.

WireGuard profiles: Downloadable .conf files from your provider dashboard. I keep short names like protonus, protonbe (or protonnl), protonin.

Packages:

sudo apt update
sudo apt install -y nftables wireguard-tools resolvconf dnsutils curl jq

If you prefer systemd-resolved over resolvconf, that’s fine. The core requirement is simple: wg-quick must be able to set resolvers on up and restore them on down.

Step 1 — Start clean 🧹

Old firewall rules are like old coffee: best case they do nothing, worst case they ruin your day.

sudo nft flush ruleset
sudo nft list ruleset   # should print nothing

If you’re on a VM, take a snapshot now. Future-you will send a thank-you email to past-you (and forget to hit send).

Step 2 — The minimal nftables killswitch 🛡️

Create a working directory:

mkdir -p "$HOME/vpn/scripts"

Save this as $HOME/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

    # Basic mode: allow WireGuard handshake port (works everywhere, less strict).
    # Tighten this in Step 2B by pinning the endpoint IP:port.
    udp dport 51820 accept
  }
}

What this does: everything is dropped by default, except loopback, established connections, and traffic through wg0. The only non-wg0 outbound allowed is the WireGuard handshake so the tunnel can come up.

Step 2B — Harden it (pin the handshake to your endpoint) 🔩

The “basic mode” rule allows UDP/51820 to anywhere. It’s fine for a quick start, but if you want a stricter policy, pin it to the endpoint IP and port in your .conf.

First, identify your endpoint from your WireGuard profile:

grep -n "^Endpoint" /etc/wireguard/protonus.conf

If it’s a hostname, resolve it (run this before enforcing ultra-strict rules):

# Replace HOSTNAME with what you see after Endpoint=
dig +short HOSTNAME | head -n 1

Then replace the broad line:

udp dport 51820 accept

…with an endpoint-pinned allow (example):

ip daddr 185.159.X.X udp dport 51820 accept

Note: if your provider rotates endpoint IPs, pinned rules mean you’ll need to update them when the IP changes. For travel setups, I keep “basic mode” as a reliable fallback.

Colorful pop art style knob representing control and toggles.

Step 3 — Scripts that save hours ⚡

Create three scripts: killswitch on, killswitch off, and a “status peek.”

Script 1: $HOME/vpn/scripts/kill-on

#!/usr/bin/env bash
set -euo pipefail

sudo nft -f "$HOME/vpn/killswitch.nft"
echo "[kill-on] Fail-closed policy active."

Script 2: $HOME/vpn/scripts/kill-off

#!/usr/bin/env bash
set -euo pipefail

sudo nft flush ruleset
echo "[kill-off] Firewall flushed (traffic is NOT restricted)."

Script 3: $HOME/vpn/scripts/kill-status

#!/usr/bin/env bash
set -euo pipefail

echo "=== nftables (first lines) ==="
sudo nft list ruleset | sed -n '1,120p' || true
echo
echo "=== wg ==="
wg || true

Make them executable:

chmod +x "$HOME/vpn/scripts/"*

Persistence (only when stable): Don’t persist while you’re still experimenting. When you’re happy:

sudo systemctl enable nftables
sudo sh -c 'nft list ruleset > /etc/nftables.conf'

Step 4 — Panic button 🔴 (instant blackout)

This is for the moments when “only VPN traffic” isn’t enough and you want zero packets leaving your machine. Captive portals, sketchy Wi-Fi, “what is that network name,” etc.

Save this as $HOME/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; }
}

$HOME/vpn/scripts/panic-on

#!/usr/bin/env bash
set -euo pipefail

sudo nft -f "$HOME/vpn/panic.nft"
echo "[panic-on] ALL traffic blocked."

$HOME/vpn/scripts/panic-off

#!/usr/bin/env bash
set -euo pipefail

sudo nft flush ruleset
echo "[panic-off] Traffic unblocked (no firewall rules)."

Make them executable too:

chmod +x "$HOME/vpn/scripts/panic-"*

Quick proof:

"$HOME/vpn/scripts/panic-on"
ping -c 1 1.1.1.1   # should fail instantly
"$HOME/vpn/scripts/panic-off"
Alert icon representing panic mode and emergency blocking.

Step 5 — DNS leak guard (strict, but simple) 🌐🔍

DNS is the classic “silent failure.” Your public IP can look fine while your resolver still whispers sweet nothings to your ISP.

5A — Make sure a DNS manager exists

resolvectl status 2>/dev/null || systemctl status systemd-resolved 2>/dev/null || true

If nothing is managing resolvers, wg-quick can’t reliably swap DNS on connect/disconnect.

5B — Set DNS inside your WireGuard profile

Inside your /etc/wireguard/protonXX.conf, under [Interface]:

DNS = 10.2.0.1

Use the resolver address your provider documents for WireGuard (the example above is common, but verify yours).

5C — Enforce “DNS must go through the tunnel”

Add these lines to the output chain in your killswitch.nft (below the wg0 allow):

IPv4 (recommended baseline):

ip daddr != 10.2.0.1 udp dport 53 drop

IPv6 (optional): only if your setup actually uses IPv6 inside the tunnel and you know the resolver address.

ip6 daddr != fd00:10:2::1 udp dport 53 drop

Then re-apply:

"$HOME/vpn/scripts/kill-on"

Browser DoH note: DNS-over-HTTPS can bypass OS DNS. During testing, disable DoH temporarily, confirm the OS path is correct, then re-enable DoH inside the tunnel if you want.

“A VPN without a leak policy is a false sense of security. Always test DNS, IPv6, and browser leaks after setup.”

Electronic Frontier Foundation

Step 6 — One-click dock buttons 🖱️✨

Typing commands is where I personally start making “creative mistakes.” Dock buttons reduce mistakes and speed up your workflow.

Create a launcher file like $HOME/.local/share/applications/vpn-kill-on.desktop:

[Desktop Entry]
Name=VPN Kill-On
Exec=/bin/bash -lc "$HOME/vpn/scripts/kill-on"
Icon=security-high
Type=Application
Terminal=true

Repeat for Kill-Off, Panic-On, Panic-Off, and (optionally) region switching. You can pin these to the dock/menu depending on your desktop environment.

Padlock symbolizing fail-closed security policy.

Multi-region switching 🌍

Keep profiles named predictably. Script: $HOME/vpn/scripts/switch-region

#!/usr/bin/env bash
set -euo pipefail

REG="${1:-us}"  # us|be|nl|in (adapt to your names)

# Bring everything down first (prevents double tunnels / weird routes)
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

# Now bring the requested region up
sudo wg-quick up "proton${REG}"
echo "[switch-region] active: proton${REG}"

Make it executable:

chmod +x "$HOME/vpn/scripts/switch-region"

Step 7 — My 5-minute verification routine ✅🧪

This routine is what turns “I think it’s working” into “I verified it.”

# 1) Enforce fail-closed
"$HOME/vpn/scripts/kill-on"

# 2) Bring up a region
sudo wg-quick up protonus

# 3) Handshake visible?
wg

# 4) Public IP should NOT be your ISP
curl -s ifconfig.io; echo

# 5) DNS sanity check (basic signal)
dig +short TXT whoami.cloudflare @1.1.1.1

# 6) Bring it down
sudo wg-quick down protonus

# 7) Still blocked? (must FAIL)
ping -c 1 1.1.1.1

If ping works after the tunnel is down, your policy isn’t fail-closed. Reapply kill-on and inspect the ruleset.

Why nftables (not iptables/UFW)? 🔧

I started with UFW because it’s friendly. But once you want a tight policy (“only wg0 + exactly what’s needed to establish the tunnel”), UFW becomes awkward. iptables works, but nftables is the modern firewall stack and it’s easier to keep one clean ruleset for IPv4/IPv6.

  • Cleaner syntax that’s easier to audit later.
  • Unified handling of IPv4/IPv6 (fewer blind spots).
  • Simple default-deny policies (fail-closed by design).

“nftables is the future of Linux firewalls: cleaner syntax and a modern replacement for iptables.”

Linux Handbook

Warning sign representing common mistakes and troubleshooting.

Common mistakes (so you don’t repeat my pain) 🚨

  • Forgetting the handshake allow rule → tunnel never comes up.
  • Assuming DNS is “fine” → test, then enforce.
  • Two tunnels at once → confusing routes and unpredictable behavior.
  • Pinning endpoint IP without a plan for IP rotation → strict rules suddenly block your connect.
  • Forgetting to export /etc/nftables.conf after changes → “why didn’t it persist?”

Stay sharp, stay invisible 👻

FAQ section background with colorful question marks.

Frequently Asked Questions ❓

❓ Do I need a paid plan to use WireGuard configs on Linux?

❓ Why not just use the official app?

❓ How do I know if DNS is leaking?

❓ Is a kill switch overkill?

❓ Can I use this approach on Ubuntu, Mint, or Debian?

VPN & Network Infrastructure Cluster

Leave a Reply

Your email address will not be published. Required fields are marked *