Skip to content

Gluetun VPN

Kheopsian edited this page Jul 29, 2026 · 6 revisions

Gluetun (VPN)

Running Hydra behind a VPN with gluetun is the fiddliest supported setup, so it gets its own page. Two things make it special:

  1. The forwarded port(s) can rotate. With port forwarding on, gluetun hands Hydra the provider's forwarded port; on some providers it changes on every reconnect. Hydra rebinds its listen port hot (no restart — torrents and live peers stay up), but something has to push the current port to it.
  2. One inbound port serves one engine. race and hoard are two processes and cannot share one inbound TCP port. So how many engines you can seed inbound behind one tunnel depends on how many ports your provider forwards.

Step 0 — how many ports does your provider forward?

This decides your whole layout, and it's a provider limit (how many ports it lets gluetun forward), not a gluetun one. gluetun does port forwarding for these providers — check your own plan, but as of writing:

Provider Ports gluetun forwards Layout
AirVPN up to 5 (you reserve them in the AirVPN panel) monolith (race + hoard) on one gluetun
Perfect Privacy multiple (3 auto + up to 5 custom) monolith on one gluetun
ProtonVPN 1, random, rotates (NAT‑PMP)¹ one engine, or front-only + one agent per gluetun
PIA 1, dynamic one engine, or split
PrivateVPN 1 per server one engine, or split

¹ ProtonVPN's NAT‑PMP can map more, but gluetun currently exposes a single port (multi-port is an open gluetun request).

Rule of thumb: ≥ 2 forwarded ports → run a monolith (race + hoard) behind one gluetun. Only 1 → run one engine behind it, or split into a front-only + one agent per gluetun (below).

gluetun mechanics (from gluetun's own docs)

  • VPN_PORT_FORWARDING=on enables it.
  • VPN_PORT_FORWARDING_UP_COMMAND runs when forwarding is set up (and on each renegotiation). Placeholders: {{PORT}} = the first forwarded port, {{PORTS}} = comma-separated list (e.g. 16171,16172).
  • FIREWALL_VPN_INPUT_PORTS = ports allowed inbound through gluetun's firewall (set these to your forwarded ports so peers can reach Hydra).
  • FIREWALL_OUTBOUND_SUBNETS = LAN subnets Hydra is allowed to reach out to.
  • The forwarded port is also exposed on gluetun's control server and (pre-v4) the file /tmp/gluetun/forwarded_port.

Hydra shares gluetun's network namespace (network_mode: service:gluetun), so its UI/API (127.0.0.1:8199) and its egress all live in the tunnel. Push the port with Hydra's HTTP API: POST /api/{race,hoard}/listen-port {"port":N}.

A) Monolith behind one gluetun — multi-port providers (AirVPN, Perfect Privacy)

Reserve two ports in AirVPN (say 16171 for race, 16172 for hoard). {{PORTS}} then expands to 16171,16172; the up-command hands one to each engine.

services:
  gluetun:
    image: qmcgaw/gluetun
    cap_add: [NET_ADMIN]
    ports:
      - "8199:8199"                    # Hydra UI, through the tunnel container
    environment:
      - VPN_SERVICE_PROVIDER=airvpn
      - VPN_PORT_FORWARDING=on
      - FIREWALL_VPN_INPUT_PORTS=16171,16172
      - FIREWALL_OUTBOUND_SUBNETS=192.168.1.0/24   # reach a LAN qBit, etc.
      - HYDRA_API_KEY=change-me
      # {{PORTS}} = "raceport,hoardport" -> push one to each engine.
      - |
        VPN_PORT_FORWARDING_UP_COMMAND=/bin/sh -c 'set -- $(echo "{{PORTS}}" | tr "," " "); wget -qO- --header="X-Api-Key: $${HYDRA_API_KEY}" --header="Content-Type: application/json" --post-data="{\"port\":$1}" http://127.0.0.1:8199/api/race/listen-port; wget -qO- --header="X-Api-Key: $${HYDRA_API_KEY}" --header="Content-Type: application/json" --post-data="{\"port\":$2}" http://127.0.0.1:8199/api/hoard/listen-port'

  hydra:
    image: ghcr.io/kheopsian/hydra:latest
    network_mode: "service:gluetun"
    depends_on: [gluetun]
    volumes:
      - ./config:/config
      - /path/to/data:/data

This is the simplest layout when your provider gives you enough ports — one container pair, both engines with real inbound.

B) Single-port providers (ProtonVPN, PIA, PrivateVPN)

You only get one port, so you can't give both engines inbound behind one tunnel. Two options:

  • One engine behind gluetun — same compose as (A) but push the single {{PORT}} to just hoard (or race):
    VPN_PORT_FORWARDING_UP_COMMAND=/bin/sh -c 'wget -qO- --header="X-Api-Key: $${HYDRA_API_KEY}" --header="Content-Type: application/json" --post-data="{\"port\":{{PORT}}}" http://127.0.0.1:8199/api/hoard/listen-port'
    
  • Inbound on both — run a --front-only controller plus one --agent-only engine per gluetun (each its own single port). See Deployment Topologies. An --agent-only node has no HTTP API, so push its port with the CLI inside the agent container (hydra set-listen-port /config/<engine>.sock <port>), driven off gluetun's forwarded-port file/control-server (a small watcher) — gluetun's UP_COMMAND runs in the gluetun container, where the hydra binary isn't.

Gotchas

  • LAN is unreachable by default. Inside the tunnel localhost is the VPN namespace, not your host, and the LAN is firewalled. Set FIREWALL_OUTBOUND_SUBNETS=192.168.x.0/24 on the gluetun service to let Hydra reach a LAN service (e.g. a qBittorrent for the import wizard); use the LAN IP, not localhost.
  • Open the forwarded ports inbound with FIREWALL_VPN_INPUT_PORTS, or peers can't reach you.
  • The listen_port in default.toml is just a placeholder behind gluetun; the real one is pushed by the up-command. Don't rely on it being fixed (dynamic-port providers change it every reconnect).
  • Expose the UI on the gluetun service (8199:8199), not on the Hydra service — Hydra has no ports of its own under network_mode: service:gluetun.

The exact UP_COMMAND (wget vs curl, quoting) varies with the gluetun image and your provider — treat the snippets above as a starting point and check them against a known-good setup.

See also Networking Modes and Deployment Topologies.

Clone this wiki locally