Repository navigation
VPN and Egress
Applies to Hydranos 4.4.
For anyone who wants Hydranos traffic to go through a VPN or a proxy: what each of the five Network-tab modes sends where, what the kill switch blocks, which way the daemon's own requests take, and the setups that work (whole process in the VPN, one tunnel or interface per engine, a proxy). The tunnels Hydranos builds itself have their own page: Managed WireGuard.
-
The five modes of the Network tab are real. Direct, WireGuard, managed by Hydranos, Gluetun, SOCKS5 proxy and SOCKS5 + PROXY-v2 relay each change what the engines do. The mode is stored as
[network] mode(see Networking). -
Everything an engine sends follows its own way out. On Linux,
bind_interfacepins every socket of the engine: listener, uTP, peer dials, HTTP and UDP announces, DHT, LSD, webseeds, scrapes. Each engine dials with its own binding and peer id. Behind a SOCKS5 proxy, peers, announces and webseeds go through the proxy, and what UDP cannot carry is switched off. - A kill switch follows the mode. It is off in Direct mode and on in the four others. When it is on, an engine with no tunnel, no interface and no proxy is kept off the network unless you mark it direct on purpose.
-
The daemon's own requests have their own route. Tracker lists, IP-filter lists, the update check, webhooks and
.torrentURLs go the way[daemon] egresssays. By default that is the way the mode implies, and they are refused rather than sent by the default route when the kill switch is on and that way is missing. - Some traffic is never covered: local traffic, WireGuard's own encrypted packets, tracker names resolved by the host for an interface Hydranos did not create, and incoming peers on the host's port behind a SOCKS5 proxy. The list is under What stays outside.
| Mode (card) | What carries an engine's traffic | Kill switch by default | Incoming port |
|---|---|---|---|
| Direct | Its bind_interface (Linux), or the host's default route when empty |
Off |
listen_port; home router via NAT-PMP/UPnP (auto_port_forward) |
| WireGuard, managed by Hydranos | A tunnel wg-<engine> that Hydranos brings up from a provider .conf (Linux, NET_ADMIN). Details: Managed WireGuard
|
On | NAT-PMP through the tunnel, or a port typed by hand, depending on the provider |
| Gluetun | Gluetun's network namespace (the container's default route is the tunnel) | On | Read from gluetun's control server and followed |
| SOCKS5 proxy | The proxy: peer dials, HTTP announces, magnets and webseeds (socks5h://, names resolved by the proxy). DHT, LSD, UDP trackers and outgoing uTP are off |
On | None through the proxy: incoming peers reach the host's own listen port, if it is reachable |
| SOCKS5 + PROXY-v2 relay | As SOCKS5, plus a listener for a relay that forwards incoming peers with a PROXY-v2 header | On | The relay's port |
Saving a mode removes the keys of the other modes from [race], [hoard] and every extra engine's block. Fields, ports and the restart rule: Networking.
One mode per instance. [network] mode holds one value for the whole process: one instance cannot put one engine behind gluetun and another in a managed WireGuard tunnel. In WireGuard mode each engine's Way out is a .conf, Direct (default interface) or Not assigned (blocked by the kill switch); an engine can only be pinned to an interface that exists in Hydranos' own network namespace, never to another container's. To mix gluetun with managed WireGuard or direct engines, run a second instance inside gluetun (Setup 1 below) and declare it as a node (Deployment Topologies). Managed WireGuard inside gluetun's namespace is not tested: there, Direct means gluetun's tunnel, and the managed tunnels would run inside it.
bind_interface = "wg0" in an engine's section makes Hydranos apply SO_BINDTODEVICE (Linux only) to that engine's sockets. The log says [engine] every socket is pinned to device wg0. A socket that cannot be pinned is not opened. It never falls back to the default route: the listener refuses to start ("refusing to listen on the default route"), a dial fails, and if the interface is missing at startup uTP stays off ("uTP disabled rather than leak the default route; restart once the interface is up").
| Traffic | Follows the engine's interface? | Details |
|---|---|---|
Peer listener, TCP (and [::] with enable_ipv6 = true) |
✓ | Replies leave by the same interface. |
| uTP socket (UDP, same port as TCP) | ✓ | Off if it cannot be pinned. |
| Outbound peer dials | ✓ the engine's own | One dial queue per engine, with that engine's interface, peer id, uTP socket and limits. |
| HTTP/HTTPS announces, magnets | ✓ | Tracker names: the tunnel's DNS for a managed tunnel, otherwise the host's resolver (see DNS). |
| UDP announces and scrapes | ✓ | A UDP socket that cannot be pinned is not opened. |
| DHT | ✓ | Bound to the device. Off behind a SOCKS5 proxy. |
| LSD (local peer discovery) | ✓ | Multicast on the device, so a tunnelled engine never reaches the host's LAN. Off behind a SOCKS5 proxy. |
| Webseeds | ✓ | Pinned like announces. |
| NAT-PMP/UPnP to the home router | ✗ not mapped | No home mapping for an engine pinned to another interface than the default route's, nor for a tunnelled, proxied or gluetun engine. A managed tunnel asks its own gateway instead. |
| Exit-IP probe (header Network panel) | ✓ | Every 180 s, api.ipify.org / api6.ipify.org through the engine's interface. |
Tracker and IP-filter lists, update check, webhooks, .torrent URLs |
— | The daemon's route ([daemon] egress, below). |
| RSS feeds | — | The way out of the engine the feed's first enabled rule adds to (see RSS and Automatic Downloads). |
| Calls to declared nodes, qBittorrent import, gluetun control server | ✗ local | Never through a tunnel or proxy. |
Since 4.4, each engine dials with its own binding: two engines on two tunnels are two separate exits, and an engine on the default route no longer pulls the others' outgoing connections out of their tunnel. The daemon's own requests are a third owner, with their own route.
flowchart LR
subgraph proc["Hydranos process"]
RACE["race engine<br/>listener, uTP, dials,<br/>announces, DHT, LSD, webseeds"]
HOARD["hoard engine<br/>listener, uTP, dials,<br/>announces, DHT, LSD, webseeds"]
DAE["daemon requests<br/>tracker and IP-filter lists,<br/>update check, webhooks, .torrent URLs"]
LOCAL["node calls, qBittorrent import,<br/>gluetun control server"]
end
RACE --> WG0["wg0<br/>race bind_interface"]
HOARD --> WG1["wg1<br/>hoard bind_interface"]
DAE --> EG["daemon egress<br/>auto, direct, proxy or engine:id"]
LOCAL --> LAN["local network"]
WG0 --> VPNA["VPN server A"]
WG1 --> VPNB["VPN server B"]
EG --> SEL["the way the setting resolves to,<br/>or refused"]
VPNA --> NET["peers, trackers, DHT nodes"]
VPNB --> NET
[daemon] kill_switch decides whether an engine with no way out of its own may use the host's default route:
[daemon] kill_switch |
Effect |
|---|---|
| absent (default) | Follows the mode: disarmed in Direct, armed in the four others. |
false |
Disarmed: an engine with no way out leaves by the host. It is reported NOT covered in the four other modes, but as direct (direct mode: the host's default route) in Direct mode. |
true |
Armed even in Direct mode: nothing leaves unless it has an interface, a proxy or allow_direct. |
allow_direct = true in an engine's section ([race], [hoard] or an extra engine's block; an extra engine does not inherit it) marks it direct on purpose. In WireGuard mode the same choice is Direct (default interface) in the engine's Way out list.
Each engine gets one verdict, decided when it joins the network:
flowchart TD
E["engine at startup"] --> M{"mode is WireGuard?"}
M -->|yes| T{"tunnel assigned?"}
T -->|yes| C1["covered: its tunnel"]
T -->|no| WD{"Direct chosen for it?"}
WD -->|yes| D1["direct on purpose"]
WD -->|no| AR
M -->|no| I{"bind_interface set?"}
I -->|yes| GI{"Gluetun mode and not the<br/>default route's interface?"}
GI -->|yes| AR
GI -->|no| C2["covered: every socket pinned"]
I -->|no| P{"SOCKS5 proxy set?"}
P -->|yes| C3["covered: through the proxy"]
P -->|no| G{"Gluetun mode?"}
G -->|yes| C4["covered: gluetun's firewall"]
G -->|no| AD{"allow_direct?"}
AD -->|yes| D2["direct on purpose"]
AD -->|no| DM{"Direct mode, disarmed?"}
DM -->|yes| D3["direct"]
DM -->|no| AR{"kill switch armed?"}
AR -->|yes| B["BLOCKED: kept off the network"]
AR -->|no| U["NOT covered: leaves by the host"]
-
Blocked means never put on the network: no listener, no dial, no announce, no DHT, no LSD, no port mapping, no exit-IP probe and no network check in its name. Its torrents stay loaded. The log says
kill switch: BLOCKED, kept off the network (no listener, no dial, no announce, no DHT, no LSD): …, and/healthreportsdegradedwith a warning naming the engine (neverunhealthy). -
Decided at startup. Changing the mode,
kill_switchorallow_directso that another set of engines is blocked needs a restart; the save answer saysrestart_required. - A pinned engine is covered because a missing interface means no network for it, never the default route. A managed tunnel that is down is the same.
- Gluetun mode trusts the declared mode: Hydranos does not check that it really runs inside gluetun's namespace.
The verdicts are given three times in the same words: at startup (kill switch: … lines, one per engine and one for the daemon), on GET /api/network/egress (and in kill_switch of GET /api/network/mode), and on the Network tab.
The card Kill switch and the daemon's own requests is shown in every mode, under the mode's fields. It holds:
- the state line
Kill switch armedorKill switch disarmed, with the reason; - one line per engine:
<engine>: covered,direct on purpose,BLOCKEDorNOT covered, with how it leaves, its gaps, andtakes effect at the next restartwhen the file no longer matches the running engine. A Direct on purpose box per engine writesallow_direct(not shown in the WireGuard and Gluetun modes); -
External requests of the daemon and Kill switch (
Follow the network mode,Disarmed: leaks accepted,Armed, even in direct mode); -
Proxy host, Proxy port, Username, Password, Daemon interface (the
[proxy]section and[daemon] bind_interface); - a fold-out What the kill switch does not cover, and the button Save daemon traffic.
Everything that belongs to no engine goes through one client: tracker-list downloads, IP-filter lists, the update check (and hydranos-update), workflow webhooks, .torrent URLs fetched by the MCP server, and the process's exit-IP probe (/api/public-ip). Its route is set by [daemon] egress:
[daemon] egress |
Route |
|---|---|
empty or auto (default) |
Follows the mode: SOCKS5/PROXY-v2: race's proxy, else the first engine with one. WireGuard: race's tunnel, else the first tunnelled engine. Gluetun: direct, inside gluetun's network. Direct: the default route, bound to [daemon] bind_interface when set. With the kill switch armed and nothing to borrow, every request is refused. |
direct |
The host's default route (or [daemon] bind_interface). A choice: never refused. |
proxy |
The [proxy] section, as socks5h:// (names resolved by the proxy). Refused if socks5_host is empty. |
engine:<id> |
The same way out as that engine: its tunnel, interface or proxy. Refused if that engine is blocked or not on this node. |
| anything else | Every request refused: [daemon] egress = "…" is not auto, direct, proxy or engine:<id>; nothing is sent until it is. |
[daemon]
egress = "proxy" # auto, direct, proxy or engine:<id>
bind_interface = "" # the daemon's requests, or its connection to the proxy
# kill_switch = true # absent: follow the mode
[proxy]
socks5_host = "198.51.100.7"
socks5_port = 1080 # 0 means 1080
socks5_user = ""
socks5_pass = ""-
Applied at once. The route changes at the next request after a save, with no restart. The log says
egress: the daemon's own requests now go via …. -
Refusals name the reason in the error of each request and on the Network tab (
The daemon's own requests are refused: …). With the kill switch on in Direct mode and nothing set, the reason beginskill switch on in direct mode: set [daemon] egress …. - An interface that is not there refuses the request rather than using another one.
-
API:
GET /api/network/egressreturns the report (mode,armed,armed_why, each engine's verdict, the daemon'svia,not_covered).POST /api/network/egresstakes any ofegress,socks5_host/socks5_port/socks5_user/socks5_pass(an empty host removes[proxy]),bind_interface,kill_switch(true,false, ornull/"auto"to follow the mode) andallow_direct({"<engine>": true}). It answers the new report withrestart_required.
The SOCKS5 passwords read ******** in GET /api/network/mode (fields.socks5_pass), GET /api/network/egress (daemon.socks5_pass) and GET /api/settings; posting the mask back keeps the stored password.
Known bug in 4.4: an
announce_proxyURL looks as private as the passwords — it is not masked, so auser:passinside it is returned in clear byGET /api/settingsto anyone holding the API key — treat the API key as giving access to that credential (Security and Access).
| Who resolves | Where tracker and host names are resolved |
|---|---|
Engine behind a SOCKS5 proxy, or with announce_proxy
|
By the proxy when the URL is socks5h:// (always so for the SOCKS5 proxy). announce_proxy is used as written: with socks5:// the name is resolved here, with http:///https:// it is handed to the proxy (behaviour of the HTTP library, not tested). |
| Engine in a managed WireGuard tunnel | By the DNS = server of its .conf, through the tunnel, never by the host. A .conf without DNS = falls back to the host's resolver and is flagged as a name leak. |
| Engine pinned to an interface Hydranos did not create | By the host's resolver (a gap the kill switch reports). |
Daemon through [proxy]
|
By the proxy. |
| Daemon through an interface without a proxy | By the tunnel's DNS if it is a managed tunnel, otherwise by the host's resolver. |
Only names leave this way: the connections themselves still follow the engine's interface.
The kill switch never covers these, whatever the configuration (not_covered in the API, and logged as kill switch: outside its scope -- … when it is armed):
- Local traffic stays local: the gluetun control server, other Hydranos nodes, a qBittorrent being imported, and NAT-PMP/UPnP to the home router for an engine that is not pinned.
- WireGuard's own encrypted packets leave by the default route: they are the tunnel.
- An engine pinned to an interface Hydranos did not create resolves tracker names with the host's resolver.
- The daemon's requests through an interface without a proxy resolve names with the host's resolver, unless that interface is a managed tunnel.
- An engine behind a SOCKS5 proxy still accepts incoming peers on the host's own listen port.
Known bug in 4.4: the SOCKS5 card looks like it hides you from incoming peers ("Nobody can connect to you from outside") — incoming peers still reach the host's listen port if it is reachable — close that port on your router if it must not be reached. The same card also says the DHT still shows your address: it does not, the DHT is off behind the proxy.
Use it when everything must go through the VPN, including traffic Hydranos does not route itself. Hydranos runs in a network namespace whose default route is the tunnel, so every flow leaves through it.
The Hydranos container joins gluetun's network namespace with network_mode: "service:gluetun". A container in that mode has no ports of its own, so its ports are published on the gluetun service. Choose the Gluetun mode on the Network tab: Hydranos then reads gluetun's forwarded port and follows it, and the kill switch is armed (gluetun's firewall is the kill switch). Unraid template fields: Unraid behind gluetun on Install with Docker.
flowchart LR
LAN["browser, Sonarr, Radarr"] -->|"8199"| GPORTS
subgraph gl["gluetun container network namespace"]
GPORTS["published port 8199"]
CTL["gluetun control server<br/>127.0.0.1:8000"]
HY["hydranos container<br/>engines and daemon requests"]
TUN["VPN tunnel<br/>default route"]
GPORTS --> HY
HY -->|"GET /v1/portforward"| CTL
HY --> TUN
end
TUN --> NET["peers, trackers, DHT"]
NET -->|"forwarded port P"| TUN
The hydranos service is adapted from the canonical compose of Install with Docker (pinned tag, 150 s stop period); only the network lines change. Everything under gluetun: is gluetun's own configuration: the variable names are the ones gluetun usually documents, Hydranos's code does not check them, and this block is not tested by the maintainer. See the gluetun documentation for your provider and for its control server's API keys.
services:
gluetun:
image: qmcgaw/gluetun
container_name: gluetun
cap_add:
- NET_ADMIN
devices:
- /dev/net/tun:/dev/net/tun
environment:
VPN_SERVICE_PROVIDER: "your-provider"
VPN_TYPE: "wireguard"
WIREGUARD_PRIVATE_KEY: "your-key"
# Provider with port forwarding: gluetun requests the port and opens it;
# Hydranos reads it from the control server (Gluetun mode).
VPN_PORT_FORWARDING: "on"
# Provider with a fixed port you already know: drop the line above
# and open the port yourself instead.
# FIREWALL_VPN_INPUT_PORTS: "47000"
# Your LAN, so Hydranos can reach qBittorrent (import), webhook
# targets and declared nodes there.
FIREWALL_OUTBOUND_SUBNETS: "192.0.2.0/24"
volumes:
- ./gluetun:/gluetun
ports:
# Hydranos's ports are published HERE, not on the hydranos service.
- "8199:8199/tcp" # web UI, REST API, qBittorrent shim, MCP
restart: unless-stopped
hydranos:
image: ghcr.io/kheopsian/hydranos:v4.4.1
container_name: hydranos
network_mode: "service:gluetun" # share gluetun's network namespace
depends_on:
- gluetun
restart: unless-stopped
stop_grace_period: 150s
ulimits:
nofile:
soft: 1048576
hard: 1048576
environment:
PUID: "1000" # Unraid: 99
PGID: "1000" # Unraid: 100
logging:
driver: json-file
options:
max-size: "128m"
max-file: "5"
volumes:
- ./config:/config # Unraid: a pool path, see Install with Docker
- ./data:/dataPeer ports are not published. Inbound peers arrive through the tunnel on the forwarded port; publishing 16171 or 16172 on the host would only open them on your LAN.
Then, on the Network tab, pick Gluetun and fill in:
-
Take the listen port from gluetun: on. Hydranos asks the control server
/v1/portforward(then/v1/openvpn/portforwardedon a 404, for older gluetun), every 10 s until a port is known and every 60 s after. The engine's listener (TCP and uTP) moves to that port, and announces are held until it does: a tracker is never told a guessed port. A port that cannot be bound keeps them held. -
Gluetun control server: empty means
http://127.0.0.1:8000, which is right when Hydranos shares gluetun's network. -
Gluetun API key: sent as
X-API-Key. Recent gluetun versions refuse requests without one; the key's role needsGET /v1/portforward. A 401/403 is logged asgluetun port forward: cannot read the portwithset the gluetun API key, with GET /v1/portforward allowed. - Engine that takes the port: hoard or race. A provider forwards one port, so one engine is reachable; the others keep their own listen port and get no inbound peers.
Save, then Apply & restart (a clean stop, resume flush included). Notes:
-
No gluetun answer, no announces. Until the control server gives a port, the engine that follows it announces nothing. The port it follows is not written to
default.toml. -
No home port mapping in Gluetun mode (
behind gluetun the port comes from the VPN provider):auto_port_forwardneeds no change. If you keep Direct mode inside gluetun instead, setauto_port_forward = falseand write the forwarded port aslisten_portby hand. -
The daemon's requests go direct inside gluetun's network (
egress = auto), which is the tunnel. -
Sonarr, Radarr, autobrr reach Hydranos at host
gluetun, port8199, when they share a user-defined Docker network with gluetun (a compose network is one). The namehydranosresolves to nothing in this setup. On Docker's default bridge network, container names do not resolve: use the Docker host's address and port 8199. -
LAN services from inside gluetun. gluetun's firewall blocks traffic to your LAN unless the subnet is in
FIREWALL_OUTBOUND_SUBNETS(gluetun's behaviour): without it, the qBittorrent import, webhooks to LAN hosts and declared nodes on the LAN fail. -
When gluetun restarts or is recreated, the Hydranos container keeps the old, dead network namespace: no network and no web UI until you restart Hydranos too (
docker restart -t 150 hydranos). Docker's behaviour, not tested by the maintainer;depends_ononly orders the first start. A tunnel reconnect inside a running gluetun keeps the namespace. - When the tunnel drops, whether traffic is blocked meanwhile is up to gluetun's firewall. Do not pin an engine to another interface in this mode: the kill switch blocks an engine pinned beside gluetun's tunnel.
The same rule without Docker: the default route seen by the hydranos process must be the tunnel.
-
Whole host on the VPN: a WireGuard or OpenVPN client that routes all traffic (for WireGuard,
AllowedIPs = 0.0.0.0/0, ::/0). Everything the host sends, Hydranos included, goes through the tunnel. -
Only Hydranos on the VPN: create a network namespace that holds the tunnel interface and uses it as its default route, then start the service inside it (for example with systemd's
NetworkNamespacePath=in a drop-in for the unit described in Install on Linux). This is done with your system's tools, outside Hydranos. Keep port 8199 reachable from where you use the web UI.
In both cases leave bind_interface empty, keep Direct mode and set the provider's forwarded port as listen_port. To keep the host's own route and still put the engines in a VPN without building anything yourself, use Managed WireGuard instead.
Each engine leaves through its own exit, inside one process, while the host keeps its normal connection. Two ways:
-
Managed WireGuard: give each engine a provider
.conf; Hydranos brings upwg-<engine>, routes it in a table of its own, asks for the incoming port and resolves names through the tunnel. See Managed WireGuard. - Your own interfaces, in Direct mode: create the tunnels outside Hydranos, then pin each engine.
For the second way:
-
Create the tunnels, for example with
wg-quick up wg0andwg-quick up wg1, withTable = offin each WireGuard config so that a tunnel does not take over the host's default route. -
Pin each engine to its interface, either in Config › Advanced settings › Network (Interface per engine: Race engine interface, Hoard engine interface, and one row per extra engine) or in
default.toml:[race] listen_port = 16171 bind_interface = "wg0" [hoard] listen_port = 16172 bind_interface = "wg1"
The drop-downs list, and + Add an engine on the Nodes tab accepts, only interfaces whose kernel state is exactly
up(otherwise the form answersno interface named … is up). Tunnel devices often reportunknowninstead (seen on a tun device; not checked on awg-quickinterface). If yours is missing from the list, writebind_interfaceindefault.tomlby hand; the drop-down keeps a value already in the file. For extra engines see Engines: Race and Hoard. -
Restart Hydranos. Check the log for
every socket is pinned to device …once per engine.
Exact limits:
- Linux only. Elsewhere the key is refused (below).
-
The kill switch is off in Direct mode. An engine you left without an interface uses the default route. Set
[daemon] kill_switch = trueto block any engine that has no interface, no proxy and noallow_direct. -
The interface must exist in Hydranos's network namespace. In Docker that means
network_mode: hostwith the tunnels on the host, or tunnels created inside the container (which needsNET_ADMIN); a container on the default bridge network does not see the host'swg0. -
If the interface is missing at startup, that engine's listener does not open (the API reports it as not listening), its dials fail and its uTP stays off until a restart finds the interface. Nothing leaves by the default route. A hand-edited
bind_interfacenaming a missing interface does not stop Hydranos from booting. - Tracker names are resolved by the host's resolver for an interface Hydranos did not create (the connections still use the interface).
-
Privileges.
SO_BINDTODEVICEneedsCAP_NET_RAWon kernels older than 5.7. Running as root (the Docker default) has it. WithPUID/PGIDin Docker or the packaged systemd user on an old kernel, pinning may fail and the listener then refuses to open (not tested).
Two per-engine keys, in [race], [hoard] or an extra engine's block:
| Key | Effect |
|---|---|
announce_proxy |
A proxy URL for HTTP announces, magnets and webseeds of this engine (socks5h://user:pass@host:port, socks5://, http://, https://). Wins over the engine's SOCKS5 proxy. Empty: the SOCKS5 proxy if the engine has one, else TYPHON_ANNOUNCE_PROXY, else direct. |
announce_ip |
Sent to trackers as ip= (BEP 7) in HTTP announces, and in the IPv4 field of UDP announces. Empty: nothing is sent, and the tracker records the address the announce comes from. |
The Network tab shows them only in the SOCKS5 modes (Announce proxy (optional), Address announced to trackers (optional)), and saving any other mode removes them. Written by hand in Direct mode, they are still read.
TYPHON_ANNOUNCE_PROXY (an environment variable, see Configuration: Environment Variables, CLI and Signals) is now a fallback: it applies to an engine with neither announce_proxy nor a SOCKS5 proxy. The Network tab lists it under Set in the environment, and warns when it is set outside the SOCKS5 modes.
environment:
TYPHON_ANNOUNCE_PROXY: "socks5h://192.0.2.2:1080"-
UDP trackers are refused while an engine's announces go through a proxy. Their status says
UDP trackers do not go through a SOCKS5 proxy (no UDP ASSOCIATE): they will not be announced while this proxy is active. Torrents whose only trackers areudp://are then not announced at all. -
A URL that cannot be used fails the announce (
announce proxy … is not usable); it never goes direct instead. - Not covered by an announce proxy alone: peer connections, DHT, LSD. For those use the SOCKS5 mode or an interface.
-
bind_interfaceis Linux-only. Windows cannot pin a socket to an interface by name, and macOS cannot pin the TCP peer sockets. A non-empty value is refused with 400 byPOST /api/network/modeandPOST /api/engines(bind_interface is Linux-only: this platform cannot pin every socket of an engine to an interface (TCP peers would leave by the default route). Run the VPN client system-wide instead). The Network tab shows, under Interface per engine, Pinning an engine to an interface is Linux-only: on this platform its peer connections would leave by the default route. Run your VPN client system-wide instead. instead of the fields. An engine whose file still sets the key starts off the network (no listener, dial, announce, DHT, LSD or uTP), with an error in the log. -
Managed WireGuard is Linux-only: the card is greyed with
Managed WireGuard needs Linux. On this system, bring the tunnel up yourself and pick its interface in Direct mode., and the API answers 409. - Gluetun only makes sense in Docker on Linux.
-
The SOCKS5 modes and
[daemon] egress = "proxy"do not depend on the platform (not tested by the maintainer on Windows). -
System-wide VPN is the way to put a Windows engine in a VPN (the Setup 1 rule: the default route is the tunnel). The kill switch does not see a system VPN: with it armed, mark engines
allow_direct(or leave the kill switch disarmed). - Automatic port forwarding tries UPnP only there (TCP and UDP); NAT-PMP needs Linux. No macOS build is published. More on Install on Windows.
Known bug in 4.4: Daemon interface looks usable on Windows — the Network tab and
POST /api/network/egressaccept[daemon] bind_interface— but every request of the daemon (lists, update check, webhooks,.torrentURLs) is then refused with[daemon] bind_interface … cannot be applied on this platform— leave Daemon interface empty and use[proxy]. On macOS it is applied.
The header's Network panel shows what each engine's probe sees through its interface (or nothing for a blocked engine), and the process line is the daemon's route. The Network tab's Check what actually happens button measures, for each local engine, the announce path and the peer path by the way they really take, and the daemon's requests. A path that comes out with the default route's address while another way is configured is a fail with LEAK: ….
Known bug in 4.4: Check what actually happens looks like a leak test in every mode — with the kill switch armed (every mode but Direct by default) the default route is not measured, so each pinned or proxied row is a
warn("a leak cannot be ruled out") and noLEAKcan be found — check from outside as below.
Exit address of a route (Linux host):
curl -s https://api.ipify.org; echo # default route
curl -s --interface wg0 https://api.ipify.org; echo # through wg0In the gluetun setup. The Hydranos image ships no curl, but it has ip, and its API reports the daemon's exit address:
docker inspect -f '{{.HostConfig.NetworkMode}}' hydranos # container:<id of gluetun>
docker exec hydranos ip route # default route via the tunnel
curl -s -H "X-Api-Key: YOUR_API_KEY" "http://127.0.0.1:8199/api/public-ip?refresh=1"
docker run --rm --network container:gluetun curlimages/curl -s https://api.ipify.org
docker run --rm --network container:gluetun curlimages/curl -6 -s -m 10 https://api6.ipify.org/api/public-ip answers {"ip": …, "ip_v6": …} for the daemon's route: both must be VPN addresses or empty. It keeps the last address it found when a lookup fails, so after a tunnel drop it can show an old value. The last line checks IPv6: an error or a VPN address is fine, your home IPv6 address is a leak (an engine announces over IPv6 when a route exists, unless enable_ipv6 = false, see Networking). GET /api/network/engines?refresh=1 gives the same per engine, and GET /api/port-forward says per engine whether inbound peers really arrived.
Watch your physical interface while Hydranos runs (replace eth0 by your LAN interface, usually br0 on Unraid, where you are already root and can drop sudo, and 203.0.113.5 by your VPN server's address). With every engine covered, only the tunnel's own packets, local traffic and DNS queries for interfaces Hydranos did not create should appear:
sudo tcpdump -ni eth0 'not host 203.0.113.5 and not port 22'
sudo tcpdump -ni eth0 host tracker.example.org # then click Reannounce on one torrentAsk the tracker. Most trackers show, on your profile or in a torrent's peer list, the address and port your client announced. That is what matters for announces: the VPN address, and the forwarded port for the engine that has it. To test inbound, point any external port checker at that address and port while Hydranos runs.
If something leaves where it should not, see Troubleshooting; the full list of gaps is on Known Limitations in 4.4. To choose between one instance, more engines or several machines, see Deployment Topologies.
Hydranos wiki — documents v4.4.1. · 🇬🇧 Home · 🇫🇷 Accueil · Changelog · Report an issue
Hydranos 4.4 · 🇬🇧
Start
Install
- Installation & First Run
- Docker
- Linux (bare metal)
- Windows
- Migrating from qBittorrent
- Upgrading from 3.x
Use
- Categories & Routing
- Adding Torrents & Existing Data
- RSS & Automatic Downloads
- Queue, Share & Speed Limits
- Engines: Race & Hoard
- Race Drain & Graduation
- Trackers & Announces
- Jobs, Bulk Actions & Moving Data
- Workflows
Integrate
Network & machines
- Deployment Topologies
- Networking: Ports & Port Forwarding
- VPN & Egress
- Managed WireGuard
- Nodes & Multiple Machines
Operate
- Operating Hydranos
- Monitoring & Logs
- Prometheus Metrics
- Security & Access
- Troubleshooting
- Known Limitations in 4.4
Reference
- Architecture
- Configuration overview
- Config: daemon, auth & global
- Config: engines & race drain
- Config: environment, CLI & signals
Hydranos 4.4 · 🇫🇷
Démarrer
Installer
- Installation & premier démarrage
- Docker
- Linux (bare metal)
- Windows
- Migrer depuis qBittorrent
- Mettre à jour depuis la 3.x
Utiliser
- Catégories & routage
- Ajouter des torrents & données existantes
- Flux RSS & téléchargements automatiques
- File, limites de partage & de débit
- Moteurs : race & hoard
- Drain race & graduation
- Trackers & annonces
- Jobs, actions groupées & déplacement
- Workflows
Intégrer
Réseau & machines
- Topologies de déploiement
- Réseau : ports & redirection
- VPN & sortie réseau
- WireGuard géré
- Nœuds & plusieurs machines
Exploiter
- Exploiter Hydranos
- Supervision & journaux
- Métriques Prometheus
- Sécurité & accès
- Dépannage
- Limites connues de la 4.4
Référence
- Architecture
- Configuration : vue d'ensemble
- Config : démon, auth & global
- Config : moteurs & drain race
- Config : environnement, CLI & signaux