Skip to content

Gluetun VPN

Kheopsian edited this page Jul 30, 2026 · 6 revisions

🇬🇧 English · 🇫🇷 Français

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)

These providers give you fixed forwarded ports: you reserve them in the provider's panel and they don't change. So there is nothing dynamic to push — no UP_COMMAND. Just set each engine's listen port statically in default.toml to your reserved ports, and open them inbound on the firewall.

Say you reserved 16171 (race) and 16172 (hoard) in the AirVPN panel.

./config/default.toml:

[race]
listen_port = 16171

[hoard]
listen_port = 16172
services:
  gluetun:
    image: qmcgaw/gluetun
    cap_add: [NET_ADMIN]
    ports:
      - "8199:8199"                    # Hydra UI, through the tunnel container
    environment:
      - VPN_SERVICE_PROVIDER=airvpn
      - VPN_TYPE=wireguard
      # WireGuard values from AirVPN's Config Generator (Client Area):
      - WIREGUARD_PRIVATE_KEY=your-private-key
      - WIREGUARD_PRESHARED_KEY=your-preshared-key
      - WIREGUARD_ADDRESSES=10.128.0.2/32
      - SERVER_COUNTRIES=Netherlands                # optional: pick your exit
      - FIREWALL_VPN_INPUT_PORTS=16171,16172       # open your reserved ports inbound
      - FIREWALL_OUTBOUND_SUBNETS=192.168.1.0/24   # reach a LAN qBit, etc.

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

Both engines get real inbound, the ports are baked into the config, and nothing has to run on reconnect. This is the simplest layout when your provider gives you enough ports.

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: on fixed-port providers (AirVPN, Perfect Privacy) this IS the real port — set it to your reserved port. Only on dynamic-port providers (Proton, PIA, PrivateVPN) is it a placeholder that the up-command overwrites on 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