<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Gabriel</title>
    <description>The latest articles on Forem by Gabriel (@gab_builds).</description>
    <link>https://forem.com/gab_builds</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3916708%2F88976e4d-7899-47ed-9dca-cd785e33e8ff.png</url>
      <title>Forem: Gabriel</title>
      <link>https://forem.com/gab_builds</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/gab_builds"/>
    <language>en</language>
    <item>
      <title>How to Actually Set Up the Gluetun VPN Killswitch</title>
      <dc:creator>Gabriel</dc:creator>
      <pubDate>Wed, 06 May 2026 21:48:31 +0000</pubDate>
      <link>https://forem.com/gab_builds/how-to-actually-set-up-the-gluetun-vpn-killswitch-49j6</link>
      <guid>https://forem.com/gab_builds/how-to-actually-set-up-the-gluetun-vpn-killswitch-49j6</guid>
      <description>&lt;p&gt;Most guides show you how to configure Gluetun. Almost none of them show you how to verify the killswitch is actually working — not just configured.&lt;/p&gt;

&lt;p&gt;This is the gap. You can have Gluetun running, qBittorrent connecting through it, and still have a broken killswitch that leaks your real IP the moment the VPN drops. I learned this the hard way.&lt;/p&gt;

&lt;p&gt;Here's how to set it up correctly and test it properly.&lt;/p&gt;




&lt;h2&gt;
  
  
  What the Killswitch Actually Does
&lt;/h2&gt;

&lt;p&gt;Gluetun is a VPN client that runs as a Docker container. The killswitch ensures that if the VPN connection drops, all traffic from dependent containers stops completely - it doesn't fall back to your real IP.&lt;/p&gt;

&lt;p&gt;The key mechanism: instead of connecting containers to Gluetun over a network bridge, dependent containers (qBittorrent, Radarr, Sonarr, Prowlarr) share Gluetun's network stack directly using &lt;code&gt;network_mode: service:gluetun&lt;/code&gt;. This means they have no independent network access — if Gluetun goes down, they go offline.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Compose Setup
&lt;/h2&gt;

&lt;p&gt;Here's the correct pattern. Pay attention to &lt;code&gt;network_mode&lt;/code&gt; — this is where most configs go wrong:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;gluetun&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;qmcgaw/gluetun&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gluetun&lt;/span&gt;
    &lt;span class="na"&gt;cap_add&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;NET_ADMIN&lt;/span&gt;
    &lt;span class="na"&gt;devices&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/dev/net/tun:/dev/net/tun&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;8080:8080&lt;/span&gt;   &lt;span class="c1"&gt;# qBittorrent web UI&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;7878:7878&lt;/span&gt;   &lt;span class="c1"&gt;# Radarr&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;8989:8989&lt;/span&gt;   &lt;span class="c1"&gt;# Sonarr&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;9696:9696&lt;/span&gt;   &lt;span class="c1"&gt;# Prowlarr&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;VPN_SERVICE_PROVIDER=nordvpn&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;VPN_TYPE=wireguard&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;WIREGUARD_PRIVATE_KEY=${WIREGUARD_PRIVATE_KEY}&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;SERVER_COUNTRIES=${SERVER_COUNTRIES}&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;

  &lt;span class="na"&gt;qbittorrent&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;lscr.io/linuxserver/qbittorrent:latest&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;qbittorrent&lt;/span&gt;
    &lt;span class="na"&gt;network_mode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;service:gluetun&lt;/span&gt;   &lt;span class="c1"&gt;# ← this is the killswitch&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;PUID=${PUID}&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;PGID=${PGID}&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;TZ=${TZ}&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;WEBUI_PORT=8080&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;${CONFIG_PATH}/qbittorrent:/config&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;${DOWNLOADS_PATH}:/downloads&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;gluetun&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;

  &lt;span class="na"&gt;radarr&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;lscr.io/linuxserver/radarr:latest&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;radarr&lt;/span&gt;
    &lt;span class="na"&gt;network_mode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;service:gluetun&lt;/span&gt;   &lt;span class="c1"&gt;# ← same for all dependent services&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;PUID=${PUID}&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;PGID=${PGID}&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;TZ=${TZ}&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;${CONFIG_PATH}/radarr:/config&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;${MOVIES_PATH}:/movies&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;${DOWNLOADS_PATH}:/downloads&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;gluetun&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Important:&lt;/strong&gt; All ports for dependent containers must be declared on the &lt;code&gt;gluetun&lt;/code&gt; service, not on the individual containers. Since they share Gluetun's network, they don't have their own ports to expose.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Verifying the Killswitch Works
&lt;/h2&gt;

&lt;p&gt;This is the step most guides skip. Don't assume it's working — test it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1 - Confirm traffic is going through the VPN
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker &lt;span class="nb"&gt;exec &lt;/span&gt;qbittorrent curl &lt;span class="nt"&gt;-s&lt;/span&gt; ifconfig.me
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This should return your VPN's IP address, not your home IP. If it returns your home IP, your &lt;code&gt;network_mode&lt;/code&gt; config is wrong.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2 - Test the killswitch
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Stop Gluetun&lt;/span&gt;
docker stop gluetun

&lt;span class="c"&gt;# Try to make a network request from qBittorrent&lt;/span&gt;
docker &lt;span class="nb"&gt;exec &lt;/span&gt;qbittorrent curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;--max-time&lt;/span&gt; 5 ifconfig.me
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the killswitch is working correctly, the curl command should &lt;strong&gt;hang and timeout&lt;/strong&gt; — not return any IP at all. If it returns your home IP, the killswitch is not working.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3 - Restore
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker start gluetun
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Wait 10-15 seconds for the VPN to reconnect, then verify the VPN IP is back:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker &lt;span class="nb"&gt;exec &lt;/span&gt;qbittorrent curl &lt;span class="nt"&gt;-s&lt;/span&gt; ifconfig.me
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Common Issues
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;curl returns your home IP after stopping Gluetun&lt;/strong&gt;&lt;br&gt;
Your containers aren't actually using &lt;code&gt;network_mode: service:gluetun&lt;/code&gt;. Double-check the compose file — it's easy to have it on one container but not others.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;qBittorrent web UI isn't accessible&lt;/strong&gt;&lt;br&gt;
Make sure the port is declared on the &lt;code&gt;gluetun&lt;/code&gt; service, not on &lt;code&gt;qbittorrent&lt;/code&gt;. This catches a lot of people.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Gluetun won't start&lt;/strong&gt;&lt;br&gt;
Check that &lt;code&gt;/dev/net/tun&lt;/code&gt; exists on your host:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; /dev/net/tun
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If it doesn't exist, run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; /dev/net &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;sudo mknod&lt;/span&gt; /dev/net/tun c 10 200
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;VPN keeps reconnecting&lt;/strong&gt;&lt;br&gt;
Try switching to a server closer to your location using the &lt;code&gt;SERVER_COUNTRIES&lt;/code&gt; environment variable in your &lt;code&gt;.env&lt;/code&gt; file.&lt;/p&gt;




&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;The killswitch isn't magic — it's just Docker networking. Once you understand that &lt;code&gt;network_mode: service:gluetun&lt;/code&gt; removes independent network access from dependent containers, the whole thing clicks.&lt;/p&gt;

&lt;p&gt;The test in Step 2 is the one thing worth doing even if you skip everything else. A killswitch you haven't tested is the same as no killswitch.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I put together a full homelab starter kit with this mediastack config included, along with Immich, Jellyfin, Pi-hole, and Portainer — each with setup guides covering the non-obvious parts. If you're building out a similar setup, &lt;a href="https://gabonix.gumroad.com/l/homelab-starter-kit" rel="noopener noreferrer"&gt;grab it here&lt;/a&gt; — launch code **LAUNCH&lt;/em&gt;* get it for $7 instead of the regular $9.*&lt;/p&gt;

</description>
      <category>docker</category>
      <category>selfhosted</category>
      <category>homelab</category>
      <category>vpn</category>
    </item>
  </channel>
</rss>
