-
Notifications
You must be signed in to change notification settings - Fork 1k
Tunnels Guide
Source of truth:
src/lib/{cloudflaredTunnel,ngrokTunnel,tailscaleTunnel}.ts,src/app/api/tunnels/Last updated: 2026-05-13 — v3.8.0
OmniRoute can expose its local server (http://localhost:20128) to the public
internet via three tunnel backends. This is useful for:
- OAuth callbacks from cloud providers (Antigravity, Gemini, Cursor) that need a publicly reachable redirect URL.
- Sharing your local instance with teammates without deploying a VM.
- Mobile, remote, or cross-network testing.
All three backends are managed in-process — OmniRoute starts/stops the underlying binary or SDK from the dashboard or REST API. No reverse-proxy or systemd setup is required.
| Backend | Persistence | Cost | Setup |
|---|---|---|---|
| Cloudflare Quick Tunnel | Ephemeral (URL changes each restart) | Free | Zero — auto-installs cloudflared
|
| ngrok | Stable while a paid plan or fixed domain is configured | Free tier + paid | Requires ngrok account + authtoken |
| Tailscale Funnel | Stable per node within your tailnet | Free for personal | Requires Tailscale install + login + Funnel ACL |
The implementations live in src/lib/cloudflaredTunnel.ts,
src/lib/ngrokTunnel.ts, and src/lib/tailscaleTunnel.ts. All three return a
common-shaped status object with phase, running, publicUrl, apiUrl,
targetUrl, and lastError fields, so the dashboard can render them uniformly.
src/lib/cloudflaredTunnel.ts runs cloudflared tunnel --url http://localhost:<apiPort> as a child process and parses the assigned
*.trycloudflare.com URL from stdout.
Key behaviors:
-
Auto-install. On first use, OmniRoute downloads the latest
cloudflaredbinary from the official GitHub releases (managed install lives underDATA_DIR/cloudflared/). SHA256 of the downloaded asset is verified against the release manifest before execution. -
Quick-tunnel only. The current implementation runs only the
--url-style quick tunnel. Named/persistent tunnels (cloudflared tunnel login+cloudflared tunnel route dns ...) are not orchestrated by OmniRoute. URLs are ephemeral and will change every restart. -
Process supervision. The cloudflared PID and resolved URL are persisted to
cloudflared-state.jsonso the dashboard can resume status across reloads.
The endpoint uses an {action: "enable" | "disable"} body, not separate
start/stop paths. Management auth (admin session or admin API key) is
required.
# Enable
curl -X POST http://localhost:20128/api/tunnels/cloudflared \
-H "Content-Type: application/json" \
-H "Cookie: auth_token=..." \
-d '{"action":"enable"}'
# Status
curl http://localhost:20128/api/tunnels/cloudflared \
-H "Cookie: auth_token=..."
# Disable
curl -X POST http://localhost:20128/api/tunnels/cloudflared \
-H "Content-Type: application/json" \
-H "Cookie: auth_token=..." \
-d '{"action":"disable"}'Or via dashboard: Settings → Tunnels → Cloudflare.
| Variable | Purpose |
|---|---|
CLOUDFLARED_BIN |
Override the binary path. If set and valid, OmniRoute uses it instead of downloading. |
CLOUDFLARED_PROTOCOL / TUNNEL_TRANSPORT_PROTOCOL
|
Transport protocol (default http2). |
src/lib/ngrokTunnel.ts uses the @ngrok/ngrok SDK (in-process, no CLI
subprocess). The native module is imported lazily on first start so platforms
without prebuilt binaries do not break the app at boot.
- Sign up at https://ngrok.com.
- Copy your authtoken from the ngrok dashboard.
- Provide it either via:
-
.env:NGROK_AUTHTOKEN=<token>, or - Dashboard: Settings → Tunnels → ngrok, or
- REST body (one-shot):
{"action":"enable","authToken":"<token>"}.
-
If neither is configured, status returns phase: "needs_auth".
# Enable (uses NGROK_AUTHTOKEN from env)
curl -X POST http://localhost:20128/api/tunnels/ngrok \
-H "Content-Type: application/json" \
-H "Cookie: auth_token=..." \
-d '{"action":"enable"}'
# Enable with inline token
curl -X POST http://localhost:20128/api/tunnels/ngrok \
-H "Content-Type: application/json" \
-H "Cookie: auth_token=..." \
-d '{"action":"enable","authToken":"2abc..."}'
# Status
curl http://localhost:20128/api/tunnels/ngrok \
-H "Cookie: auth_token=..."
# Disable
curl -X POST http://localhost:20128/api/tunnels/ngrok \
-H "Content-Type: application/json" \
-H "Cookie: auth_token=..." \
-d '{"action":"disable"}'The response includes the assigned publicUrl (e.g.
https://abcd-1234.ngrok-free.app). Custom domains, regions, and policy rules
must be configured in the ngrok dashboard — OmniRoute itself only forwards the
local target URL to the SDK.
src/lib/tailscaleTunnel.ts orchestrates the system tailscale CLI to expose
the local API port via Funnel (Tailscale's public-internet egress for serve).
It supports the full lifecycle: install, login, daemon start, enable, disable.
The implementation invokes tailscale funnel --bg <port> (background mode). The
public URL has the shape https://<machine>.<tailnet>.ts.net/.
- Install Tailscale (or let OmniRoute do it — see
installendpoint below). - Sign in (
tailscale loginor via OmniRoute'sloginendpoint). - Enable Funnel for your tailnet in the Tailscale admin console: https://login.tailscale.com/admin/settings/features.
On Linux and macOS the daemon (tailscaled) requires sudo to control. The
POST endpoints accept an optional sudoPassword field which is forwarded to
OmniRoute's MITM password cache (getCachedPassword / setCachedPassword) for
the duration of the call. Windows uses the default service install at
C:\Program Files\Tailscale\tailscale.exe.
Tailscale has a richer surface than the other backends because installation, login, daemon, and tunnel are separate concerns.
| Endpoint | Method | Purpose |
|---|---|---|
/api/tunnels/tailscale |
GET |
Aggregated tunnel status (phase, tunnelUrl, apiUrl, etc.) |
/api/tunnels/tailscale/check |
GET |
Lower-level check: installed? logged in? daemon running? |
/api/tunnels/tailscale/install |
POST |
Install Tailscale (SSE-streamed progress events) — Linux/macOS |
/api/tunnels/tailscale/start-daemon |
POST |
Start tailscaled on Linux/macOS |
/api/tunnels/tailscale/login |
POST |
Begin login flow; returns authUrl to open in a browser |
/api/tunnels/tailscale/enable |
POST |
Start the Funnel for the API port |
/api/tunnels/tailscale/disable |
POST |
Stop the Funnel |
All Tailscale endpoints require management auth (see routeUtils.ts :: requireTailscaleAuth).
Example enable:
curl -X POST http://localhost:20128/api/tunnels/tailscale/enable \
-H "Content-Type: application/json" \
-H "Cookie: auth_token=..." \
-d '{"sudoPassword":"<linux-pwd>","port":20128}'If Funnel is not enabled in the admin console, the response includes
funnelNotEnabled: true plus an enableUrl to open in a browser.
| Variable | Purpose |
|---|---|
TAILSCALE_BIN |
Override the tailscale binary path |
| Endpoint | Method | Body | Auth |
|---|---|---|---|
/api/tunnels/cloudflared |
GET |
— | management |
/api/tunnels/cloudflared |
POST |
{action: "enable" | "disable"} |
management |
/api/tunnels/ngrok |
GET |
— | management |
/api/tunnels/ngrok |
POST |
{action, authToken?} |
management |
/api/tunnels/tailscale |
GET |
— | management |
/api/tunnels/tailscale/check |
GET |
— | management |
/api/tunnels/tailscale/install |
POST |
{sudoPassword?} (SSE) |
management |
/api/tunnels/tailscale/start-daemon |
POST |
{sudoPassword?} |
management |
/api/tunnels/tailscale/login |
POST |
{hostname?} |
management |
/api/tunnels/tailscale/enable |
POST |
{sudoPassword?, hostname?, port?} |
management |
/api/tunnels/tailscale/disable |
POST |
{sudoPassword?} |
management |
There is no central /api/settings/tunnels endpoint — each backend is
independent.
When you expose OmniRoute through a tunnel, the dashboard and OAuth flows must
build callback URLs against the public hostname, not localhost. Otherwise
the OAuth provider redirects the user back to a URL its servers cannot reach,
and the handshake fails.
Set:
NEXT_PUBLIC_BASE_URL=https://<your-tunnel-host>and restart OmniRoute before initiating OAuth. For ephemeral Cloudflare Quick Tunnels the URL changes after every restart, so prefer ngrok with a reserved domain or Tailscale Funnel for production OAuth use.
The dashboard surfaces tunnel state under Settings → Tunnels:
- Active backend(s) and current
phase(stopped,starting,running,needs_auth,error). - The current public URL and the derived API URL (
<publicUrl>/v1). - The local target URL the tunnel is forwarding to.
- Last error message, if any.
For programmatic monitoring poll the per-backend GET endpoints. Running more
than one backend simultaneously is allowed; OmniRoute will track each
independently.
OmniRoute attempts to auto-install on first use. If the install is blocked
(restricted network, no GitHub access), download cloudflared manually from
https://github.com/cloudflare/cloudflared/releases and set
CLOUDFLARED_BIN=/path/to/cloudflared.
phase: "needs_auth" means no authtoken was found. Set NGROK_AUTHTOKEN in
.env, configure it via the dashboard, or pass authToken in the enable POST
body.
When the enable response includes funnelNotEnabled: true, Funnel is disabled
for your tailnet. Open the returned enableUrl (or the admin console feature
page) and toggle Funnel on.
Use ngrok with a reserved domain or Tailscale Funnel (both stable per-node). Cloudflare Quick Tunnels are ephemeral by design and not recommended for long-lived OAuth callbacks.
tailscaled needs root. Provide sudoPassword to the relevant POST endpoint,
or run the daemon yourself (sudo systemctl start tailscaled).
- PROXY_GUIDE.md — outbound proxy (1proxy, SOCKS5, HTTP) for egress traffic.
-
ENVIRONMENT.md — full list of env vars including
NEXT_PUBLIC_BASE_URL. - FLY_IO_DEPLOYMENT_GUIDE.md, DOCKER_GUIDE.md — alternatives to tunneling for stable public hosting.
- Source:
src/lib/{cloudflaredTunnel,ngrokTunnel,tailscaleTunnel}.ts,src/app/api/tunnels/.
OmniRoute · Website · npm · Docker Hub
- Setup Guide
- User Guide
- Features
- Quick Start (Docker)
- Electron Desktop App
- Termux (Android)
- PWA Guide
- MCP Server
- A2A Server
- Agent Protocols
- OpenCode Plugin
- Webhooks
- Cloud Agents
- Skills
- Memory
- Evals
- Gamification
- Guardrails
- Compliance
- Error Sanitization
- Public Credentials
- Route Guard Tiers
- Stealth Guide
- CLI Token Auth