When running privacy-sensitive applications through a VPN, one of your biggest concerns should be connection drops. If your VPN disconnects unexpectedly, your real IP address gets exposed—defeating the entire purpose of using a VPN in the first place. A VPN kill switch solves this problem by blocking all non-VPN traffic automatically.

đź’ˇ This article contains affiliate links. If you buy through them, we earn a small commission at no extra cost to you. Learn more.

In this guide, we’ll build a robust, self-hosted VPN kill switch using Docker containers and iptables firewall rules. You’ll learn how to create an isolated network namespace where applications can only communicate through the VPN tunnel—if the VPN drops, all traffic stops completely. We’ll use qBittorrent as our example application, but the same approach works for any Docker container.

Why You Need a VPN Kill Switch

A VPN kill switch is essential for several reasons:

IP Leak Prevention: Without a kill switch, applications continue using your real IP when the VPN disconnects, exposing your actual location and identity.

Automated Protection: Manual monitoring isn’t reliable. A kill switch works automatically 24/7 without requiring your attention.

Privacy Compliance: If you’re in a jurisdiction with strict privacy requirements, leaked connections could have legal consequences.

Torrent Safety: For P2P file sharing, even a brief IP exposure can be logged by monitoring services.

No Manual Intervention: You won’t need to constantly check if your VPN is active or restart applications when reconnections happen.

Understanding the Docker VPN Kill Switch Architecture

Our setup uses Docker’s network isolation capabilities combined with iptables rules to create an impenetrable barrier. Here’s how it works:

  1. VPN Container: Runs your VPN client (OpenVPN, WireGuard, etc.) and establishes the tunnel
  2. Network Namespace Sharing: Application containers share the VPN container’s network stack
  3. iptables Rules: Firewall rules inside the VPN container block all traffic except through the VPN interface
  4. Automatic Enforcement: If the VPN tunnel goes down, packets simply can’t route anywhere

This architecture is superior to application-level kill switches because it operates at the network layer—applications can’t bypass it even if they wanted to.

Prerequisites

Before starting, make sure you have:

  • A Linux server or homelab setup running Docker and Docker Compose
  • Root or sudo access to configure iptables
  • An active VPN subscription with OpenVPN or WireGuard configuration files
  • Basic understanding of Docker networking concepts
  • SSH access to your server

For hardware recommendations, a basic Intel N100 mini PC works perfectly for VPN routing and multiple containers.

Step 1: Create the VPN Container

We’ll start by creating a VPN container using the popular dperson/openvpn-client image. This container will establish and maintain the VPN connection.

Create a new directory for your project:

1
2
mkdir -p ~/docker/vpn-killswitch
cd ~/docker/vpn-killswitch

Create a docker-compose.yml file:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
version: '3.8'

services:
  vpn:
    image: dperson/openvpn-client
    container_name: vpn
    cap_add:
      - NET_ADMIN
    devices:
      - /dev/net/tun
    environment:
      - FIREWALL=on
      - VPN_AUTH=${VPN_AUTH}
    volumes:
      - ./vpn-config:/vpn
    restart: unless-stopped
    sysctls:
      - net.ipv6.conf.all.disable_ipv6=1
    dns:
      - 1.1.1.1
      - 1.0.0.1
    ports:
      - "8080:8080"  # qBittorrent WebUI

The critical components here:

  • cap_add: NET_ADMIN: Grants permission to modify network interfaces
  • devices: /dev/net/tun: Required for creating the VPN tunnel
  • FIREWALL=on: Enables the built-in kill switch mechanism
  • sysctls: Disables IPv6 to prevent IPv6 leaks

Step 2: Configure Your VPN Connection

Place your VPN provider’s configuration files in the vpn-config directory:

1
mkdir vpn-config

Copy your .ovpn file from your VPN provider:

1
cp ~/Downloads/your-provider.ovpn vpn-config/vpn.conf

If your VPN requires authentication, create a .env file:

1
echo "VPN_AUTH=username;password" > .env

Some VPN providers use different authentication methods. Check your provider’s documentation for the correct format.

Step 3: Implement Custom iptables Kill Switch Rules

While the dperson/openvpn-client image includes basic firewall rules, we’ll add custom iptables rules for maximum security. Create a script that runs when the VPN container starts:

1
2
mkdir -p vpn-config/scripts
nano vpn-config/scripts/firewall.sh

Add this comprehensive firewall configuration:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#!/bin/bash

# Flush existing rules
iptables -F
iptables -X
iptables -t nat -F
iptables -t nat -X
iptables -t mangle -F
iptables -t mangle -X

# Set default policies to DROP everything
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT DROP

# Allow loopback traffic (essential for container communication)
iptables -A INPUT -i lo -j ACCEPT
iptables -A OUTPUT -o lo -j ACCEPT

# Allow established and related connections
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
iptables -A OUTPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

# Allow LAN access (adjust subnet to match your network)
iptables -A INPUT -s 192.168.0.0/16 -j ACCEPT
iptables -A OUTPUT -d 192.168.0.0/16 -j ACCEPT

# Allow VPN connection establishment (UDP 1194 for OpenVPN)
iptables -A OUTPUT -p udp --dport 1194 -j ACCEPT

# Allow DNS queries before VPN is up
iptables -A OUTPUT -p udp --dport 53 -j ACCEPT
iptables -A OUTPUT -p tcp --dport 53 -j ACCEPT

# Allow all traffic through the VPN tunnel interface
iptables -A OUTPUT -o tun+ -j ACCEPT
iptables -A INPUT -i tun+ -j ACCEPT

# Block everything else (kill switch)
iptables -A OUTPUT -j DROP
iptables -A INPUT -j DROP

# Log dropped packets for debugging (optional)
iptables -A OUTPUT -j LOG --log-prefix "VPN-KILLSWITCH-DROP: " --log-level 4

Make the script executable:

1
chmod +x vpn-config/scripts/firewall.sh

Update your docker-compose.yml to run this script:

1
2
3
4
5
6
services:
  vpn:
    # ... previous configuration ...
    volumes:
      - ./vpn-config:/vpn
      - ./vpn-config/scripts/firewall.sh:/etc/openvpn/up.sh:ro

Step 4: Add Application Containers to the VPN Network

Now we’ll add qBittorrent as an example application that routes all traffic through the VPN kill switch. Add this to your docker-compose.yml:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
  qbittorrent:
    image: linuxserver/qbittorrent
    container_name: qbittorrent
    network_mode: "service:vpn"
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=Europe/Berlin
      - WEBUI_PORT=8080
    volumes:
      - ./qbittorrent/config:/config
      - ./qbittorrent/downloads:/downloads
    restart: unless-stopped
    depends_on:
      - vpn

The crucial line is network_mode: "service:vpn" — this makes qBittorrent share the VPN container’s network namespace. All traffic from qBittorrent now must go through the VPN container, subject to our kill switch rules.

Step 5: Start and Verify Your Kill Switch

Launch the containers:

1
docker-compose up -d

Check that the VPN connected successfully:

1
docker logs vpn

You should see confirmation of the VPN connection and tunnel interface creation.

Step 6: Test the Kill Switch

This is the most important step—verifying that your kill switch actually works.

Test 1: Check IP Address Through VPN

1
docker exec vpn curl -s https://ifconfig.me

This should return your VPN server’s IP address, not your real IP.

Test 2: Verify qBittorrent Uses VPN IP

1
docker exec qbittorrent curl -s https://ifconfig.me

This should return the same IP as the VPN container.

Test 3: Kill Switch Test (Critical)

Stop the VPN container while qBittorrent is running:

1
docker stop vpn

Now try to access the internet from qBittorrent:

1
docker exec qbittorrent curl -s --max-time 10 https://ifconfig.me

This should timeout completely with no response. If you get any IP address back, your kill switch isn’t working correctly.

Test 4: Check for DNS Leaks

1
docker exec vpn curl -s https://www.dnsleaktest.com/

Visit the URL provided and verify all DNS servers belong to your VPN provider, not your ISP.

Advanced Configuration Options

Using WireGuard Instead of OpenVPN

If you prefer WireGuard for better performance, modify your compose file:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
  vpn:
    image: linuxserver/wireguard
    container_name: vpn
    cap_add:
      - NET_ADMIN
      - SYS_MODULE
    environment:
      - PUID=1000
      - PGID=1000
    volumes:
      - ./wireguard-config:/config
    sysctls:
      - net.ipv4.conf.all.src_valid_mark=1
      - net.ipv6.conf.all.disable_ipv6=1
    restart: unless-stopped

The same kill switch principles apply—iptables rules block traffic when the WireGuard interface goes down.

Multiple Applications Behind the Same VPN

You can route multiple containers through the same VPN kill switch:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
  radarr:
    image: linuxserver/radarr
    container_name: radarr
    network_mode: "service:vpn"
    # ... rest of configuration ...

  sonarr:
    image: linuxserver/sonarr
    container_name: sonarr
    network_mode: "service:vpn"
    # ... rest of configuration ...

Remember to expose all required ports on the VPN container:

1
2
3
4
5
  vpn:
    ports:
      - "8080:8080"   # qBittorrent
      - "7878:7878"   # Radarr
      - "8989:8989"   # Sonarr

Adding Port Forwarding for Torrents

Many VPN providers offer port forwarding for better torrent performance. Check your provider’s documentation for setup instructions. You’ll typically need to:

  1. Enable port forwarding in your VPN account dashboard
  2. Note the assigned port number
  3. Configure qBittorrent to use that specific port
  4. Add the port to your VPN container’s exposed ports

Automatic VPN Reconnection

The unless-stopped restart policy ensures the VPN container automatically restarts if it crashes. For additional reliability, add health checks:

1
2
3
4
5
6
7
8
  vpn:
    # ... previous configuration ...
    healthcheck:
      test: ["CMD", "ping", "-c", "1", "1.1.1.1"]
      interval: 60s
      timeout: 10s
      retries: 3
      start_period: 30s

Monitoring and Troubleshooting

Check VPN Connection Status

Monitor the VPN tunnel status:

1
docker exec vpn ip addr show tun0

If tun0 doesn’t exist, the VPN isn’t connected.

View Firewall Rules

Inspect active iptables rules:

1
docker exec vpn iptables -L -v -n

You should see DROP policies and rules allowing traffic only through tun+ interfaces.

Check for IP Leaks in Logs

If you enabled packet logging, monitor drops:

1
docker exec vpn dmesg | grep VPN-KILLSWITCH

Performance Issues

If you experience slow speeds:

  1. Try different VPN server locations
  2. Switch from OpenVPN to WireGuard
  3. Adjust MTU settings in your VPN configuration
  4. Verify your network hardware supports gigabit speeds

Container Won’t Start

Check Docker logs for errors:

1
docker-compose logs vpn

Common issues:

  • Missing /dev/net/tun device
  • Incorrect VPN credentials
  • Firewall blocking VPN ports on your host
  • IPv6 conflicts

Security Best Practices

Disable IPv6 Completely: IPv6 can bypass your VPN kill switch if not properly configured. Our configuration disables it at the container level.

Use DNS Provided by VPN: Never use your ISP’s DNS servers. Configure your VPN provider’s DNS or use privacy-respecting options like 1.1.1.1 or 9.9.9.9.

Regular Testing: Schedule monthly kill switch tests to verify your protection remains intact after updates.

Monitor Logs: Set up log monitoring to detect VPN disconnections and reconnections.

Keep VPN Software Updated: Regularly update your VPN container images:

1
2
docker-compose pull
docker-compose up -d

Alternative Solutions

While our Docker-based approach offers excellent isolation, other options exist:

Network-Wide VPN: Configure your router to route all traffic through a VPN. This protects all devices but lacks per-application control.

Split Tunneling: Route only specific traffic through the VPN. More complex to configure but offers flexibility.

Gluetun: A specialized Docker image that supports multiple VPN providers with built-in kill switch, port forwarding, and HTTP/SOCKS5 proxy capabilities.

Conclusion

You now have a bulletproof VPN kill switch that prevents IP leaks at the network level. This Docker-based approach offers several advantages: isolation from your host system, easy deployment, automatic recovery, and the ability to route multiple applications through the same protected tunnel.

The kill switch works automatically without any manual intervention—if your VPN connection drops for any reason, all traffic stops immediately. No IP leaks, no manual monitoring required, just reliable protection for your privacy-sensitive applications.

Remember to test your kill switch regularly and monitor your logs for any unexpected behavior. With this setup in place, you can run torrents, access geo-restricted content, or maintain privacy-critical services with confidence that your real IP address will never leak.

For additional security, consider combining this VPN kill switch with other privacy tools like Pi-hole for DNS filtering, encrypted DNS-over-HTTPS, and regular security audits of your self-hosted infrastructure.