Setup guide · routers
Run Tunnelo on an OpenWrt or pfSense router
You don't need your router for a normal Tunnelo setup. The agent runs on your Jellyfin box, and the router never gets touched. But if the router's your one always-on device, or you'd rather it own the WireGuard tunnel, here's how to put it in the loop, including the one forwarding rule that trips people up.
First, the two jobs
Tunnelo does two things, and they can live in different places:
- The tunnel: a WireGuard peer from your network to the gateway. Routers are excellent at this.
- The agent: registers your token, then reports service health and runs the upload speed test. A small process that has to run on some always-on Linux box, but not necessarily the router.
There's one hop that's easy to miss, too: the gateway routes your address to your tunnel IP on the service port (like 10.77.0.42:8096).
Whatever holds that tunnel IP has to pass that traffic on to your actual Jellyfin. In managed mode, the agent does this for you. In external mode,
when your router carries the tunnel, the router does it with one NAT rule. That's the whole
trick.
The peer config only routes the gateway's single address (10.77.0.1/32) into the
tunnel, so this never touches your router's default route or anyone's normal browsing.
Which path is mine?
| Your situation | Do this |
|---|---|
| You have an always-on Linux box (NAS, Pi, whatever runs your apps) | Normal install on that box. The router isn't involved. Start here. |
| OpenWrt router, arm64 or x86_64, run everything on it | Agent on OpenWrt (managed mode) |
| OpenWrt router, prefer its native WireGuard (any arch) | OpenWrt WireGuard + external mode |
| pfSense router | pfSense: external mode only |
OpenWrt: agent on the router (managed mode)
The agent runs entirely on the router and handles the forwarding hop itself. Works on arm64 / x86_64 OpenWrt (check uname -m → aarch64 or x86_64). On other arches, use the external-mode path below instead.
Install kernel WireGuard, then the binary:
opkg update && opkg install kmod-wireguard # amd64 shown; on an arm64 router swap amd64 → arm64 in the URL cd /tmp wget -O t.tgz https://github.com/abiteman/tunnelo-agent/releases/latest/download/tunnelo-agent_linux_amd64.tar.gz tar -xzf t.tgz tunnelo-agent install -m 0755 tunnelo-agent /usr/bin/tunnelo-agent
Write the config. Point it at your Jellyfin's LAN IP, not localhost:
mkdir -p /etc/tunnelo-agent cat > /etc/tunnelo-agent/agent.env <<'EOF' TUNNELO_TOKEN=YOUR-TOKEN TUNNELO_GATEWAY_URL=https://api.tunnelo.app TUNNELO_SERVICE_URL=http://YOUR-JELLYFIN-IP:8096 EOF chmod 600 /etc/tunnelo-agent/agent.env
OpenWrt uses procd, not systemd, so add an init script:
cat > /etc/init.d/tunnelo-agent <<'EOF'
#!/bin/sh /etc/rc.common
USE_PROCD=1
START=95
start_service() {
procd_open_instance
procd_set_param command /usr/bin/tunnelo-agent
procd_set_param env $(cat /etc/tunnelo-agent/agent.env | grep -v '^#' | xargs)
procd_set_param respawn
procd_set_param stdout 1
procd_set_param stderr 1
procd_close_instance
}
EOF
chmod +x /etc/init.d/tunnelo-agent
/etc/init.d/tunnelo-agent enable
/etc/init.d/tunnelo-agent start
logread -f -e tunnelo-agent # watch it register + connectFinally, let the service port through the firewall. The agent's forwarder listens on the tunnel interface, which isn't in a firewall zone by default:
uci add firewall rule uci set firewall.@rule[-1].name='Allow-Tunnelo' uci set firewall.@rule[-1].src='*' uci set firewall.@rule[-1].proto='tcp' uci set firewall.@rule[-1].dest_port='8096' uci set firewall.@rule[-1].target='ACCEPT' uci commit firewall && /etc/init.d/firewall restart
No manual NAT needed here. The agent's built-in forwarder makes the tunnel-IP → Jellyfin hop for you.
OpenWrt: native WireGuard + external mode
Let OpenWrt's own WireGuard carry the tunnel (works on any arch, since no agent binary runs on the router). The agent runs on any LAN Linux box just to register and report health.
1. Get the peer config. On any always-on Linux box (the Jellyfin host is ideal), run the agent once in external mode and leave it running:
docker run -d --name tunnelo --restart unless-stopped -v tunnelo-agent:/var/lib/tunnelo-agent -e TUNNELO_TOKEN=YOUR-TOKEN -e TUNNELO_TUNNEL_MODE=external -e TUNNELO_GATEWAY_URL=https://api.tunnelo.app -e TUNNELO_SERVICE_URL=http://YOUR-JELLYFIN-IP:8096 ghcr.io/abiteman/tunnelo-agent:latest
It writes a standard wg-quick config (tunnelo-wg.conf in the volume)
and keeps heartbeating so the dashboard shows live status. The config looks like:
[Interface] PrivateKey = <private key> Address = 10.77.0.42/32 MTU = 1280 [Peer] PublicKey = <gateway key> Endpoint = wg.tunnelo.app:51820 AllowedIPs = 10.77.0.1/32 PersistentKeepalive = 25
2. Add it to OpenWrt. In LuCI: Network → Interfaces → Add, protocol WireGuard VPN; fill in the [Interface] private key and address,
then add the [Peer]. Bring it up and check wg show for a handshake.
3. Forward the hop. The router now holds the tunnel IP; redirect its service port to Jellyfin:
uci add firewall redirect uci set firewall.@redirect[-1].name='Tunnelo-Jellyfin' uci set firewall.@redirect[-1].src='*' uci set firewall.@redirect[-1].proto='tcp' uci set firewall.@redirect[-1].src_dport='8096' uci set firewall.@redirect[-1].dest_ip='YOUR-JELLYFIN-IP' uci set firewall.@redirect[-1].dest_port='8096' uci set firewall.@redirect[-1].target='DNAT' uci commit firewall && /etc/init.d/firewall restart
pfSense
pfSense is FreeBSD, and the agent's tunnel management is Linux-only, so the agent can't run on pfSense itself. Use pfSense's built-in WireGuard for the tunnel and run the agent on a LAN Linux box for health reporting.
- Get the peer config exactly as in OpenWrt step 1 above: run the agent in external mode on any LAN Linux box and leave it running.
- Create the tunnel under VPN → WireGuard → Tunnels: paste the
[Interface]private key and address, then add a peer with the gateway's public key, endpoint (wg.tunnelo.app:51820), AllowedIPs10.77.0.1/32, keepalive 25. Assign and enable the interface, and check Status → WireGuard for a handshake. - Forward the hop under Firewall → NAT → Port Forward on the
WireGuard interface: destination the tunnel address port
8096, redirect target your Jellyfin LAN IP port8096, plus the matching pass rule.
Good to know
- Multiple services? Use
TUNNELO_SERVICES=YOUR-JELLYFIN-IP:8096,7878,8989instead ofTUNNELO_SERVICE_URL(bare ports reuse the first host). In external setups, add one forward rule per port. - The agent must keep running somewhere for live status and speed-test re-runs. In external setups the tunnel stays up even if the agent stops.
- Uninstalling. A systemd bare-metal install comes off with the same
installer:
… | sudo TUNNELO_UNINSTALL=1 sh(addTUNNELO_PURGE=1to wipe credentials too). The OpenWrt managed-mode setup above uses procd, not systemd, so reverse it by hand:/etc/init.d/tunnelo-agent disable; rm /etc/init.d/tunnelo-agent /usr/bin/tunnelo-agent(and the firewall rule +/etc/tunnelo-agentif you want it fully gone). External-mode setups: stop the agent container and remove the WireGuard interface and forward rule from the router. - The full technical reference (including the userspace-WireGuard fallback and the exact config fields) lives in docs/routers.md in the agent repo.
Prefer the simple path?
If you don't specifically need the router, one command on the server itself does the whole thing. No firewall rules, no NAT.
The 5-minute setup