Multi-location reverse-tunnel system on rathole + Nginx
One port · one domain · one certificate · many foreign nodes routed by URL path. Built for censorship-resistant tunneling into Iran.
English · فارسی · Full Wiki · Quick start · Documentation · Telegram
The CLI tools live under
rathole-manager/. For the full Persian reference seedocs/README.fa.md; a Persian summary is at the bottom of this page.
- What is this
- Three roles
- Transport modes
- Quick start
- Update & rollback
- Documentation
- Repository layout
- خلاصهی فارسی
A single Iran server — behind one domain, one Let's Encrypt cert, one public port 443 — fronts many foreign nodes that connect back over a reverse tunnel. User traffic is routed to a node by URL path (map $uri $backend_port in nginx). Nothing but :443 is exposed on the Iran server; the foreign nodes have no public port.
| Role | Program | Responsibility |
|---|---|---|
| Iran panel | rathole-manager/ratholectl (bash) |
rathole server + nginx. Owns node inventory. Generates server.toml + rathole.conf. |
| Foreign node | rathole-manager/ratholenode (bash) |
rathole client. Generates client.toml. |
| Hub | rathole-manager/ratholehub/hub.py (Python, stdlib only) |
Central web panel driving many servers over SSH. |
The core design principle everywhere: change state → regenerate config → validate → hot-reload. Configs are never hand-edited; they are rewritten in place (inode preserved) so rathole hot-reloads without dropping active tunnels, with automatic rollback if nginx -t fails.
The same tunnel can be carried five ways (plus a game/SNI L4 mode) — switching never changes user services, tokens, or paths, only the carrier. Each node's carrier is exclusive: the hub exposes it as a single select (ws/kcp/plain/noise/backhaul) that coordinates both sides.
- websocket + TLS (default) —
wss://domain:443, TLS terminated by nginx. - kcp — parallel UDP+FEC path for lossy links (looks like QUIC on UDP/443).
- plain — no-TLS websocket to a separate HTTP listener (lighter, unencrypted).
- noise — encrypted transport (Noise/X25519) on a second rathole instance, no TLS/cert.
- backhaul (v1.6) — a separate Go core (
Musixal/Backhaul) beside rathole that multiplexes many user connections onto one stream via SMUX; nginx proxies the hardcoded/channel+/tunnelpaths to it on the same 443, so single-port/single-domain holds. - game / SNI — L4 passthrough on 443; TLS terminates on the node (VLESS+TLS+Vision).
- adaptive failover (v1.5.0) — timer-driven probes classify each carrier (
healthy / tls_failed / tcp_timeout / …) and auto-switch between ws and kcp with hysteresis + cooldown; plain requires explicitALLOW_INSECURE=1. - secret control path (v1.5.0) — WebSocket control upgraded from
/to/_rh/<32 hex>; nginx routes only that exact path to rathole; all other paths retain fake-site behaviour.
Details: docs/transport-modes.md.
One command from GitHub (downloads the latest release bundle, then runs the installer). Defaults to loopy-iri/RatholeEngine — override with RATHOLE_GH to use a fork:
# Fully interactive (asks panel/node + details)
curl -fsSL https://raw.githubusercontent.com/loopy-iri/RatholeEngine/main/install.sh | sudo bash
# Iran server (panel), non-interactive:
curl -fsSL https://raw.githubusercontent.com/loopy-iri/RatholeEngine/main/install.sh | sudo bash -s -- --panel \
--domain panel.example.ir \
--fullchain /root/cert/panel.example.ir/fullchain.pem \
--key /root/cert/panel.example.ir/privkey.pem
# Foreign node, non-interactive:
curl -fsSL https://raw.githubusercontent.com/loopy-iri/RatholeEngine/main/install.sh | sudo bash -s -- --node -- \
--server panel.example.ir:443 --name trk01 --token <T> --inbound-port 2087
install.shfetchesrathole-manager.zip+bootstrap.shfrom the latest GitHub Release (published by the release workflow), then hands off tobootstrap.sh. Override the source repo withRATHOLE_GH="you/repo"or pin a version withRATHOLE_RELEASE="v1.2.3".
Offline / local bundle (no download) still works via bootstrap.sh directly — see docs/README.fa.md.
Re-running the panel installer is safe: it detects a previous or partial install, prints a ✓/✗ status per component, and offers resume (complete missing pieces, keep state —
--repair, the default) or fresh (back up + wipe config/state, reinstall —--fresh). Undercurl … | bash(no TTY) it defaults to the safe resume path.
Then add nodes from the Iran panel:
ratholectl hub on <port> # enable hub on port <port>
ratholectl add trk01 2087 # → path /trk01 routed to that node
ratholectl proxy add mysvc http://127.0.0.1:9000 # /mysvc/ → any upstream, no rathole involved
ratholectl ls # list nodes + user paths
ratholectl status # full dashboard: domain, ports, cert, services, nodes (--json for the hub)
ratholectl paths # every config/file path with a ✓/✗
ratholectl version # manager_version + rathole_version
ratholectl doctor # health checkratholectl add also prints a ready-to-paste curl … | sudo bash -s -- --node -- … one-liner for the foreign node, with the token/inbound already filled in.
From the hub you can drive all of this without the CLI: Update all rolls the update across every server one-by-one (progress bar + per-server status, with a green/yellow version badge on each), add to node wires an Iran node (name/token/inbound) onto a foreign node or upstream, and set main tunnel links a node to its Iran server.
Updates take a full snapshot (CLI + configs + systemd units) into /var/backups/rathole-manager/ before touching anything, run a per-role health check afterwards, and auto-roll-back to the snapshot if the service fails to come up or nginx -t breaks.
# Update in place (auto-detects panel/node/hub; snapshot + health-check + auto-rollback)
curl -fsSL https://raw.githubusercontent.com/loopy-iri/RatholeEngine/main/install.sh | sudo bash -s -- --update
# or, from an installed tree: sudo bash /opt/rathole-manager/update.sh
sudo bash update.sh --list-backups # list snapshots
sudo bash update.sh --rollback # revert to the latest snapshot
sudo bash update.sh --rollback 20260713-2210 # revert to a specific one
sudo bash update.sh --no-rollback # update but never auto-revert (snapshot only)Full CLI reference and install flows (Persian): docs/README.fa.md.
bootstrap.sh auto-detects which roles are installed (panel / node / hub) and runs the matching uninstaller for each:
sudo bash bootstrap.sh --uninstall # remove installed roles (asks to confirm)
sudo bash bootstrap.sh --purge --yes # + rathole binary and hub config, no prompt
# or per-role directly: sudo bash uninstall-panel.sh [--purge] [--yes]TLS certs are never removed. The shared common.sh is removed only when no other role remains on the host. The same option is item 7 in the interactive bootstrap.sh menu.
| Doc | Contents |
|---|---|
| GitHub Wiki | Complete GitHub Wiki (Getting Started, CLI Reference, Adaptive Filtering, Hub, Troubleshooting). |
docs/architecture.md |
Three roles, the state→regenerate→reload principle, the path==name==map==inbound invariant. |
docs/transport-modes.md |
The four transport carriers + game/SNI, with diagram. |
docs/traffic-flow.md |
Layer-by-layer packet path (Mermaid + SVG diagrams). |
docs/hub.md |
Central web panel (hub.py + hubcmds.py + ui/): REST API, security model, allow-listed actions. |
docs/performance.md |
Tuning beyond the tunnel (BBR, kcp profiles, non-tunnel bottlenecks). |
docs/amneziawg-reverse.md |
A separate AmneziaWG reverse-tunnel design (not part of the rathole flow). |
docs/README.fa.md |
Full Persian CLI reference + install/uninstall flows. |
docs/install-manual.md |
Full manual install (English): Iran panel + Pasargad config + foreign nodes + hub, step by step. |
docs/install-manual.fa.md |
Same manual install guide, Persian. |
rathole-multilocation-pasargad.md |
Original detailed design & troubleshooting doc (Persian). |
install.sh one-command installer from GitHub (curl | sudo bash)
bootstrap.sh env prep + unpack + hand-off to installer (local or --url)
package.sh build the distributable rathole-manager.zip
.github/workflows/ release.yml (build+publish on tag v*) · ci.yml (lint)
rathole-manager/
ratholectl Iran panel CLI (rathole server + nginx)
ratholenode foreign node CLI (rathole client)
common.sh shared bash helpers
ratholehub/hub.py central web panel (stdlib Python) — router/state/SSH/parsers
ratholehub/hubcmds.py security-critical action→argv allow-list (RE_* regexes)
ratholehub/ui/ web UI assets (index.html, app.css, app.js, i18n.js)
install-*.sh per-role installers (panel/node/hub)
update.sh safe update: snapshot + health-check + rollback
uninstall-*.sh
docs/ documentation + assets/ (SVG/PNG diagrams)
Donation
tether bep-20 0x2364898407e8A42DA554B909989cd9B28eE61314
ton ton UQCondoXRwQNt0fQvvmYGa_Fn39D9szjQH4eBqIcUd5r1yfH
tron trx TRhnkJFpj9uxA2aqUxapJAFhkzNPbfxKwp
RatholeEngine یک سیستم تونل معکوس چند-موقعیتی روی rathole + Nginx است: یک سرور ایران (پشت یک دامنه، یک گواهی، یک پورت ۴۴۳) جلوی چند نود خارجی قرار میگیرد که با تونل معکوس به آن وصل میشوند و ترافیک با path به هر نود مسیریابی میشود. فقط پورت ۴۴۳ روی سرور ایران عمومی است.
سه نقش: پنل ایران (ratholectl — rathole server + nginx)، نود خارج (ratholenode — rathole client)، و هاب مرکزی (hub.py — پنل وب مدیریت چند سرور از طریق SSH).
اصل مرکزی: تغییر state → بازتولید کانفیگ → nginx -t → hot-reload (با حفظ inode و بازگشت خودکار در صورت خطا).
دستورهای مفید CLI: ratholectl status (داشبورد کامل: دامنه، پورتها، گواهی، سرویسها، نودها — با --json برای هاب)، ratholectl paths (مسیر همهی فایلها با ✓/✗)، ratholectl version (نسخهی مدیر + rathole). ratholectl add یک دستور آمادهی curl … | sudo bash -s -- --node -- … هم برای نصب نود چاپ میکند.
از هاب: دکمهی «آپدیت همه» همهی سرورها را یکییکی آپدیت میکند (progress bar + وضعیت هر سرور + badge نسخه سبز/زرد)، «افزودن به نود» یک نود ایران را روی نود/آپاستریم خارج سیمکشی میکند، و «تنظیم تونل اصلی» نود را به سرور ایرانش وصل میکند.
شروع سریع و مرجع کامل CLI (فارسی): docs/README.fa.md
فهرست مستندات فارسی: