~$ klumbsyd / tech / posts / self-hosting / wireguard-docker-kill-switch.md
post.config
1# post metadata
2title="isolating docker containers behind wireguard with a kill switch"
3author="dustin"
4date="2026-04-13"
5tags=["self-hosting", "networking", "docker", "wireguard", "unraid"]
6read_time="4 min"
7word_count="753"
8status="published"

isolating docker containers behind wireguard with a kill switch

Some containers need VPN routing. Not all of them – just the ones where the exit IP matters. The typical write-up tells you to run gluetun and attach containers to it. On UnRAID, you don’t need that. The WireGuard plugin handles it natively, and the kill switch is already wired in.

The UnRAID way

UnRAID’s VPN Manager (Settings > VPN Manager) has a tunnel type called “VPN tunneled access for docker.” That’s the one. It creates a wg0 interface and sets up policy routing so that any container assigned to it routes all traffic through the tunnel – and only through the tunnel.

Once the tunnel is configured and active, attaching a container is one setting in the UnRAID template: set Network Type to wg0. The container gets no other network interface. All traffic goes through the tunnel or nowhere.

[binhex-slskd] --> wg0 --> Mullvad

No gluetun container to maintain, no network_mode: service:gluetun compose gymnastics.

How the kill switch actually works

It’s not iptables rules. UnRAID’s WireGuard plugin uses policy routing. When the tunnel comes up, it populates a routing table (table 200) with a default route through wg0 and a local LAN route:

PostUp=ip -4 route flush table 200
PostUp=ip -4 route add default via <wg0-addr> dev wg0 table 200
PostUp=ip -4 route add <your-lan-subnet> via <your-gateway> dev br0 table 200

When the tunnel goes down:

PostDown=ip -4 route flush table 200
PostDown=ip -4 route add unreachable default table 200
PostDown=ip -4 route add <your-lan-subnet> via <your-gateway> dev br0 table 200

The unreachable default is the kill switch. Any container whose traffic routes through table 200 hits that route and gets dropped. The container goes dark – no fallback to your real IP, no silent leak.

You can confirm the table is active:

ip route show table 200

When the tunnel is up you’ll see the wg0 default. When it’s down, you’ll see unreachable default.

The DNS gotcha

This one is not obvious and it bit me. The kill switch covers traffic. DNS is a separate problem.

When you set a container’s network to wg0, Docker still assigns its own embedded resolver at 127.0.0.11. That resolver forwards upstream to whatever the host’s /etc/resolv.conf says – usually your router. The DNS query exits through your LAN interface (br0), not through wg0. Your traffic is tunneled but your DNS lookups are not.

Verify the leak before fixing it:

docker exec binhex-slskd cat /etc/resolv.conf
# Look for ExtServers -- if it shows your router IP, you're leaking

The fix: force the container to use the VPN provider’s resolver directly. In the UnRAID container template under Extra Parameters:

--dns 10.64.0.1

That’s Mullvad’s DNS – substitute your own provider’s resolver IP if you’re not on Mullvad. After restarting the container, check again:

docker exec binhex-slskd cat /etc/resolv.conf
# ExtServers should now show 10.64.0.1

DNS queries now go to Mullvad’s resolver. Since 10.64.0.1 is only reachable through the tunnel, DNS also goes dark if the tunnel drops – which is what you want.

The binhex healthcheck

binhex-slskd has a built-in healthcheck that adds a second layer on top of the kill switch. Set these environment variables in the container template:

ENABLE_HEALTHCHECK=yes
HEALTHCHECK_HOSTNAME=google.com

The binhex healthcheck is its own mechanism inside the image – it’s not Docker’s native HEALTHCHECK. When the container can’t reach the healthcheck host, the binhex script handles the restart itself without needing a separate Docker restart policy configured.

The kill switch stops the leak. The healthcheck tells you something is wrong.

Verify it

# Confirm exit IP is the VPN, not your home IP
docker exec binhex-slskd curl -s ifconfig.me

# Confirm DNS is going to the VPN resolver, not your router
docker exec binhex-slskd cat /etc/resolv.conf

# Test the kill switch: bring down the interface, confirm the container goes dark
# Option 1: toggle via Settings > VPN Manager in the UnRAID UI
# Option 2: from the host terminal
ip link set wg0 down
docker exec binhex-slskd curl -s --max-time 5 ifconfig.me  # should hang and fail
ip link set wg0 up

If the kill switch test returns anything, table 200 is not routing the container correctly. Double-check the container’s Network Type is set to wg0 and not a custom bridge or host.

What runs behind it

I use this for binhex-slskd, sharing Linux ISOs back to the Soulseek community. P2P on a visible home IP is the obvious use case.

Any container that needs a specific exit IP gets the same treatment: Network Type wg0, --dns 10.64.0.1 in Extra Parameters, healthcheck enabled.