A tunnel between your Iran server and your foreign servers that is simple to run, hard to spot, and repairs itself when filtering changes.
فارسی · English
If DEYROUTE helps you, a ⭐ helps others find it.
Quick start · Everyday use · Monitoring · How it survives filtering · Troubleshooting · Full guides
A complete tunnel manager between an Iran server and servers abroad. Instead of setting up Backhaul, Rathole, FRP, Xray Reality, Hysteria2 or Waterwall by hand, one at a time, and swapping them by hand whenever one gets filtered, DEYROUTE installs them with one command, tests them all the time and moves to the next one by itself when one is blocked.
DEYROUTE connects two kinds of servers:
- the hub: your server in Iran. Your users connect to it, and only to it.
- the nodes: your servers abroad. They run your real VPN service (Xray, Marzban, 3x-ui, and so on).
Your users only need the hub's address, so the foreign server's address stays out of their configs. Their traffic travels through the tunnel exactly as it left their device: DEYROUTE does not open, change, or re-encrypt it.
| Three steps, copy and paste | Install on the hub, paste one command on the node, create the tunnel. Every question has a suggested answer: press Enter to take it. |
| Hard to identify | Eight tunnel methods from six different programs, some made to look like ordinary HTTPS to a real website. When one is recognised and blocked, the next one starts. |
| It fixes itself | The hub checks every tunnel every 5 seconds. A blocked method is replaced automatically (goal: under 35 seconds; about 20 in our lab tests), with nobody awake. When the first method has been healthy again for a few minutes, the tunnel goes back to it. |
| Backup nodes | Add a second foreign server that runs the same VPN service (same users and settings), and the tunnel moves to it if the first one goes down. |
| Your service stays as it is | Same port, same certificate, same UUID. In your client configs only the server address changes, to the hub's IP. |
| Errors you can read | Every problem says what happened, why, and what to do, with a fixed code such as DEY-P012. |
| Private by design | One signed program, no telemetry, no secrets in logs, and only its own firewall tables. |
You need:
- Two servers with Ubuntu 22.04 / 24.04 / 26.04 or Debian 12 / 13 (amd64 or arm64): the hub in Iran, and the node abroad, which already runs your VPN service (3x-ui, Marzban, Xray ...). Check that the service works from a phone first.
rooton both (ssh root@SERVER_IP, orsudo -iafter logging in as another user), andcurl(apt-get install -y curl). No Python, no Docker. Each command below goes on the server named in its heading.- GitHub reachable from the hub. Iranian datacenters often block it; if step 1 cannot download, see installing without GitHub.
- Open ports in your provider's firewall panel (and in
ufw, if you use it), or the steps below fail without a message. Hub:44433/tcp(nodes join here), your tunnel port such as443(your users), and30000-31999TCP and UDP from the node's IP. Node:30000-31999TCP and UDP from the hub's IP.
bash <(curl -fsSL https://github.com/localroot4/DeyRoute/releases/latest/download/install.sh)A short wizard asks at most five questions, one per screen. Type 1 for the role (hub),
give the server a name such as ir-1, and press Enter for the rest.
At the end it prints a join command. Copy that whole line.
Paste the whole join command and add --name de-1 at its very end: that is the node's
id, the short name later commands use. The command works once and expires in 15 minutes:
bash <(curl -fsSL https://github.com/localroot4/DeyRoute/releases/latest/download/install.sh) join 'dey://…' --name de-1The command the hub prints may already end with --version …; keep that part and put
--name de-1 after it.
About 30 seconds later, go back to the hub. The node must be listed as online:
deyroute node listThe menu shows the same list under 3) Nodes:
Not listed? The usual cause is 44433/tcp closed in the hub's provider firewall. Open it, then run deyroute node join-command on the hub for a new command.
Use the node id from deyroute node list and the port your VPN service listens on
(it must be free on the hub; deyroute port suggest lists free ones). main is
the tunnel's id, used by the commands below:
deyroute tunnel add --node de-1 --ports 443 --name mainYou will see the steps go by, ending with a line like:
✔ Tunnel main is UP via backhaul/wssmux (41ms)
The menu does the same in 2) Tunnels → 1) Add tunnel (node, ports, confirm):
deyroute status
deyroute port check 443In your client app (or panel) replace the node's address with the hub's IP. Keep
everything else (port, UUID or password, SNI, path) exactly as it was. Then test from a
phone inside Iran: deyroute port check only proves the port is open from the
internet, it cannot see filtering in Iran.
Recommended next step: add a second foreign server the same way (it must run
the same VPN service), then make it the backup:
deyroute tunnel backup add main --node nl-1.
Type deyroute (or just dey) to open the menu. Press a number, then
Enter. q goes back and ? explains the screen you are on.
1) Dashboard shows every tunnel, node and recent event and refreshes every 2 seconds:
The same screen without the menu is deyroute status (it stays tidy on a
phone's narrow terminal too):
Everything in the menu is also a command, handy for scripts (add --json):
| I want to… | Run |
|---|---|
| see everything at a glance | deyroute status |
| add a foreign server | deyroute node join-command (on the hub), then paste the command on the new server |
| create a tunnel | deyroute tunnel add --node de-1 --ports 443,2053 |
| add or remove a port | deyroute port add main 8443 / deyroute port remove main 8443 |
| check that a port really works | deyroute port check 443 |
| watch the logs | deyroute logs main -f |
| switch method or node by hand | deyroute tunnel switch main --transport backhaul/tcpmux |
| add a backup node | deyroute tunnel backup add main --node nl-1 |
| test every method and compare latency (interrupts the tunnel, 20 s per method) | deyroute tunnel test-ladder main |
| get Telegram alerts | menu 9) Notifications |
| collect a report for support | deyroute doctor (secrets are removed) |
New to it? Stay in Simple mode: it hides everything advanced, and the tunnel wizard asks
at most three questions (node, ports, confirm). Switch in 12) Settings → 1) UI mode when you want more.
The hub counts the bytes of every tunnel and keeps the CPU and RAM of every
server, with nothing to set up. Download (to your users) and upload (from
them) are always labelled. Open 6) Diagnostics → 6) Traffic and load (or press
t on the dashboard) and pick a period: 1 hour, 2 24 hours,
3 7 days, 4 30 days.
The same from the command line, for scripts too (--json):
An optional monthly quota per tunnel sends an alert at 80 % and 100 %. More: traffic and load.
deyroute optimize auto measures every server (RAM, CPUs, kernel, network)
and lists the kernel settings that suit it: the current value, the new one,
when it takes effect and why. Nothing changes until you confirm, and
deyroute optimize revert undoes it all. The setup wizard offers the same as its
last question.
More: automatic tuning.
Filtering keeps changing, so one method is never enough. Every tunnel gets a ladder of methods from different programs. One rung carries the traffic; the others are installed, configured and waiting.
| Rung | Method | Why it is there |
|---|---|---|
| 1 | backhaul/wssmux |
TLS and WebSocket in few connections: looks like normal HTTPS |
| 2 | backhaul/tcpmux |
no TLS layer, least overhead, for when the tunnel's TLS is what gets noticed |
| 3 | rathole/noise |
a different program and protocol with Noise encryption |
| 4 | frp/tcp |
a third program with a different traffic pattern |
| 5 | xray/reality |
looks like a real TLS 1.3 visit to a harmless website |
| 6 | hysteria2/udp |
QUIC hidden by Salamander obfuscation, on a port in 30000–31999 (never 443): fastest on lossy links, when UDP passes |
| 7 | waterwall/reverse-reality |
a reverse connection that also looks like a real website (least tested method) |
| 8 | direct/native |
no disguise; only so the service does not go down |
When the hub confirms that the active rung is blocked, it starts the next one;
with a block that stays in place, users usually do not notice.
When rung 1 has been healthy again for a few minutes,
the tunnel goes back to it. The built-in ladder cannot be edited, but you can build
your own: deyroute ladder create NAME --rungs a,b,c, then
deyroute tunnel edit main --ladder NAME. Details: the filtering FAQ.
Good habits that make you harder to spot
- Give users only the hub's address. Never publish the foreign server's IP.
- Keep your service's own TLS settings (Reality, WebSocket, and so on). DEYROUTE does not replace them, it carries them.
- Optional: choose three decoy sites for the Reality rungs (real HTTPS sites with TLS 1.3, open from your hub and not blocked in Iran). Run
deyroute config editand underhub:setdecoy_snis: [site1.com, site2.com, site3.com]. - Keep a backup node in another provider or country.
- When the direct path between the hub and a node is filtered (a connection
opens, then is cut after a few exchanges), send the node and its tunnels
through Cloudflare:
deyroute front enable --domain t1.example.comon the hub, then the command it prints on the node. Guide: through Cloudflare. - No tool can promise to be invisible. DEYROUTE's job is to make recognising and blocking you expensive, and to recover quickly when it happens.
- The control channel always goes from the foreign servers out to the hub, over mutual TLS 1.3 with a private CA. The hub never logs in to a node, and SSH is not used.
- The join command works once and expires in 15 minutes.
- DEYROUTE manages its own firewall table (
inet deyroute) and, for traffic counting, a second table that only counts and never blocks anything (inet deyroute_stats). It never touches your other rules. - Every release is signed; the installer verifies the signature and checksums before installing.
- No telemetry. Secrets are stored with mode
0600and never written to logs.
More: security guide.
bash <(curl -fsSL https://github.com/localroot4/DeyRoute/releases/latest/download/install.sh) # same line = repair or upgrade
deyroute update # update deyroute itself; tunnels keep running
deyroute update backends # update the tunnel programs (Backhaul, Xray, ...)
deyroute backup # encrypted backup of the hub (asks for a passphrase)
deyroute restore FILE # on the new server, after install.sh --no-setup
deyroute hub announce-move NEW_IP:44433 # on the old hub: tell every online node
deyroute node set-hub NEW_IP:44433 # or on each node, by hand
deyroute uninstall # remove everything and restore the systemRun these on the hub unless a comment says otherwise.
Guides: update and uninstall · backup and restore · moving the hub.
Start with deyroute doctor. It checks the system, ports, certificates, units and
logs, and explains each finding in plain words.
| Symptom | Try |
|---|---|
a node is offline |
on the hub: deyroute node test <id>, then open 44433/tcp in the hub's provider firewall |
the tunnel is UP but the client cannot connect |
on the hub: deyroute port check <port> (it prints the command that opens a closed port). Also check that your VPN service runs on the node |
| the tunnel keeps switching methods | deyroute logs <tunnel>; then, when a short interruption is acceptable, deyroute tunnel test-ladder <tunnel> |
| an error with a code | read its three lines (what / why / fix); all codes are in docs/ERRORS.md |
More: troubleshooting.
- Hub and node are the same program. The role is chosen in the wizard.
- Your TLS is never touched. The tunnel carries raw bytes, so your client keeps its SNI, path, UUID and fingerprint.
- Honest limits. The node must reach the hub's control port (
44433/tcp); a few methods (Reality, Hysteria2, direct) also connect from the hub to the node. If the route between the two countries is cut completely, the tunnel stays down until it returns: DEYROUTE cannot work around that today, so please open an issue. Your VPN panel sees the hub's address, not your users' real IPs, so per-IP limits there will not work. - Early builds. Releases are currently
edgebuilds. They pass automated lab tests (systemd, nftables and the real backends in containers) but have had little real-world testing; see what is still pending in the acceptance evidence. Running the install line again upgrades in place and keeps your configuration.
| Guide | |
|---|---|
| Install | requirements, offline install, mirrors, what the installer verifies |
| Join a node | the join command, expiry, IP changes |
| First tunnel | the menu and the CLI, port syntax, health probes |
| Backup node | failover, failback, timings |
| Traffic and load | what is counted, charts, history, monthly quota |
| Automatic tuning | what is measured and changed, and how to undo it |
| Filtering FAQ | the ladder, skipped methods, decoy sites |
| Through Cloudflare | when the direct path between hub and node is filtered |
| Troubleshooting | doctor, logs, common codes |
| Security | control channel, tokens, firewall, rotation |
| Reference | error codes · --json output · backends |
| Project | architecture · releasing · decisions and open questions · changelog |
make build # static binary in ./dist (CGO_ENABLED=0)
make test lint # unit tests and lintersIntegration scenarios run in systemd containers: test/integration/run.sh
(see the header of the script). Issues and pull requests are welcome.
DEYROUTE is released under the MIT license.