OmniRoute/docs/ops/TUNNELS_GUIDE.md
Diego Rodrigues de Sa e Souza 91b6983564
Release v3.8.1 (#2441)
Release v3.8.1 — feature flags settings page, bracketed combo names, security hardening, multi-driver SQLite
2026-05-21 01:29:12 -03:00

12 KiB

title version lastUpdated
Tunnels Guide 3.8.1 2026-05-13

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.

Backends at a glance

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.

1. Cloudflare Tunnel (Quick Tunnel)

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 cloudflared binary from the official GitHub releases (managed install lives under DATA_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.json so the dashboard can resume status across reloads.

Enable / disable via REST

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.

Optional env vars

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).

2. ngrok

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.

Prerequisites

  1. Sign up at https://ngrok.com.
  2. Copy your authtoken from the ngrok dashboard.
  3. 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 / disable via REST

# 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.

3. Tailscale Funnel

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/.

Prerequisites

  1. Install Tailscale (or let OmniRoute do it — see install endpoint below).
  2. Sign in (tailscale login or via OmniRoute's login endpoint).
  3. 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.

REST endpoints

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.

Optional env vars

Variable Purpose
TAILSCALE_BIN Override the tailscale binary path

Endpoint summary

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.

OAuth callback considerations

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.

Health and monitoring

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.

Troubleshooting

"cloudflared binary not found"

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.

"ngrok: authtoken required"

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.

"tailscale: funnel not enabled"

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.

Tunnel URL changes break OAuth

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.

Permission denied on Linux/macOS for Tailscale

tailscaled needs root. Provide sudoPassword to the relevant POST endpoint, or run the daemon yourself (sudo systemctl start tailscaled).

See also