# How to Set Up Tunnels with Cloudflare Quick Tunnels or ngrok in OmniRoute

> Easily set up tunnels with Cloudflare Quick Tunnels or ngrok in OmniRoute. Expose your local router to the internet programmatically or via REST API.

- Repository: [Diego Rodrigues de Sa e Souza/OmniRoute](https://github.com/diegosouzapw/OmniRoute)
- Tags: how-to-guide
- Published: 2026-07-26

---

**OmniRoute exposes your local router to the internet via built-in Cloudflare Quick Tunnels and ngrok support, using the modular handlers in [`src/lib/cloudflaredTunnel.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/cloudflaredTunnel.ts) and [`src/lib/ngrokTunnel.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/ngrokTunnel.ts) that you can control programmatically or through REST API endpoints.**

Setting up public endpoints for local development or remote testing is a core feature of OmniRoute. This guide explains how to configure and manage both **Cloudflare Quick Tunnels** and **ngrok** using the tunneling subsystem implemented in the `release/v3.8.49` branch.

## Architecture Overview

OmniRoute isolates tunnel management from the main request pipeline to ensure that tunnel failures do not affect the LLM routing engine. The architecture consists of provider-specific core modules and corresponding HTTP route handlers.

### Core Tunnel Modules

The tunnel logic resides in dedicated library files that dynamically import binaries only when needed:

- **[`src/lib/cloudflaredTunnel.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/cloudflaredTunnel.ts)** – Launches the `cloudflared` binary, parses its stdout to capture the public URL, and maintains a global listener handle for lifecycle management.
- **[`src/lib/ngrokTunnel.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/ngrokTunnel.ts)** – Dynamically imports the `@ngrok/ngrok` package only when needed, creates a forward listener, and stores the connection state.
- **[`src/lib/tunnelManager.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/tunnelManager.ts)** *(if present)* – Provides a central registry that abstracts over both providers, letting downstream code query the current public URL without knowing which provider is active.

Both provider modules expose standardized functions—`start`, `getStatus`, and `stop`—making them interchangeable at the API layer.

### API Route Handlers

HTTP endpoints wrap the core modules for external access:

- [`src/app/api/tunnels/cloudflared/route.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/app/api/tunnels/cloudflared/route.ts) – Exposes `/api/tunnels/cloudflared/start`, `/status`, and `/stop`.
- [`src/app/api/tunnels/ngrok/route.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/app/api/tunnels/ngrok/route.ts) – Exposes `/api/tunnels/ngrok/start`, `/status`, and `/stop`.

These routes accept JSON payloads matching the programmatic function signatures and return objects containing `publicUrl` and optional `error` fields.

## Setting Up Cloudflare Quick Tunnels

Cloudflare Quick Tunnels provide ephemeral public URLs without requiring a registered domain. OmniRoute automates the `cloudflared` CLI interaction.

### Prerequisites

Ensure the `cloudflared` binary is installed on your system and accessible in your `PATH`. OmniRoute launches it as a subprocess and parses its output to extract the public URL. If the binary is missing, the start function will fail with a clear error message.

### Starting the Tunnel Programmatically

Import the `startCloudflaredTunnel` function from the core module and await the returned public URL:

```typescript
import { startCloudflaredTunnel } from '@/lib/cloudflaredTunnel';

const result = await startCloudflaredTunnel({
  hostname: 'my-router.trycloudflare.com', // optional custom subdomain
  apiToken: process.env.CLOUDFLARED_TOKEN,  // optional Cloudflare API token
});

console.log('Tunnel active at:', result.publicUrl);

```

If the binary fails to obtain a URL, the function throws the specific error: `"cloudflared could not obtain a public URL."`

### Checking Status and Stopping

Query the active tunnel state using `getCloudflaredTunnelStatus`, which returns an object with `publicUrl` and optional `error` properties. Terminate the tunnel with `stopCloudflaredTunnel`:

```typescript
import {
  getCloudflaredTunnelStatus,
  stopCloudflaredTunnel,
} from '@/lib/cloudflaredTunnel';

const status = await getCloudflaredTunnelStatus();
console.log('Current URL:', status.publicUrl ?? status.error);

// Tear down the tunnel
await stopCloudflaredTunnel(); // clears the global listener and terminates the process

```

## Setting Up ngrok Tunnels

ngrok offers TCP and HTTP tunnels with custom region selection. OmniRoute integrates via the official `@ngrok/ngrok` Node.js SDK.

### Authentication Setup

Before starting, configure your ngrok authtoken. The module checks for credentials in three locations: the `authtoken` parameter passed to `startNgrokTunnel`, the standard ngrok configuration file at `~/.ngrok2/ngrok.yml`, or the `NGROK_AUTHTOKEN` environment variable. Missing credentials surface the specific error: `"An ngrok authtoken is required."`

### Starting the Tunnel

Invoke `startNgrokTunnel` with the local port and optional region:

```typescript
import { startNgrokTunnel } from '@/lib/ngrokTunnel';

const tunnel = await startNgrokTunnel({
  addr: 3000,    // The port OmniRoute listens on
  region: 'eu',  // Optional: us, eu, ap, au, sa, jp, in
});

console.log('ngrok URL:', tunnel.publicUrl);

```

### Monitoring and Stopping

Query the tunnel status and terminate the session using the corresponding status and stop functions:

```typescript
import {
  getNgrokTunnelStatus,
  stopNgrokTunnel,
} from '@/lib/ngrokTunnel';

const status = await getNgrokTunnelStatus();
console.log('ngrok status:', status.publicUrl ?? status.error);

// Shut down the tunnel
await stopNgrokTunnel(); // closes the ngrok process and clears the listener

```

## Using the HTTP API Alternative

If you prefer command-line scripts or CI integrations, use the REST endpoints instead of importing modules directly. The CLI (`omniroute`) forwards identical payloads to these routes.

| Method | Endpoint | Body | Description |
|--------|----------|------|-------------|
| `POST` | `/api/tunnels/cloudflared/start` | `{ "hostname": "...", "apiToken": "..." }` | Starts a Cloudflare Quick Tunnel |
| `GET` | `/api/tunnels/cloudflared/status` | — | Returns `{ publicUrl, error }` |
| `POST` | `/api/tunnels/cloudflared/stop` | — | Stops the active Cloudflare tunnel |
| `POST` | `/api/tunnels/ngrok/start` | `{ "addr": 3000, "region": "eu" }` | Starts an ngrok tunnel |
| `GET` | `/api/tunnels/ngrok/status` | — | Returns `{ publicUrl, error }` |
| `POST` | `/api/tunnels/ngrok/stop` | — | Stops the active ngrok tunnel |

These routes are defined in [`src/app/api/tunnels/cloudflared/route.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/app/api/tunnels/cloudflared/route.ts) and [`src/app/api/tunnels/ngrok/route.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/app/api/tunnels/ngrok/route.ts), according to the OmniRoute source code.

## Summary

- OmniRoute provides isolated tunnel management via [`src/lib/cloudflaredTunnel.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/cloudflaredTunnel.ts) and [`src/lib/ngrokTunnel.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/ngrokTunnel.ts), ensuring tunnel failures do not crash the LLM router.
- **Cloudflare Quick Tunnels** require the `cloudflared` binary in your `PATH` and support optional `hostname` and `apiToken` parameters.
- **ngrok** tunnels require an authtoken (via param, config file, or env var), accept `addr` (port) and `region` options, and dynamically import the SDK to avoid hard dependencies.
- Use `getCloudflaredTunnelStatus` or `getNgrokTunnelStatus` to poll public URLs and `stopCloudflaredTunnel`/`stopNgrokTunnel` to terminate processes cleanly.
- HTTP endpoints at `/api/tunnels/{provider}/` expose identical functionality for non-JavaScript consumers and CI pipelines.

## Frequently Asked Questions

### Do I need to install cloudflared separately?

Yes. OmniRoute expects the `cloudflared` binary to exist in your system `PATH`. The framework does not bundle the binary but launches it as a subprocess via [`src/lib/cloudflaredTunnel.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/cloudflaredTunnel.ts), parsing stdout to extract the public URL. If the binary is missing, `startCloudflaredTunnel` will fail with an actionable error message.

### Where does OmniRoute store the ngrok authtoken?

The ngrok module in [`src/lib/ngrokTunnel.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/ngrokTunnel.ts) checks for the authtoken in three locations, in order: the `authtoken` parameter passed directly to `startNgrokTunnel`, the standard ngrok configuration file at `~/.ngrok2/ngrok.yml`, or the `NGROK_AUTHTOKEN` environment variable. If none are found, the function rejects with `"An ngrok authtoken is required."`

### Can I run both Cloudflare and ngrok tunnels simultaneously?

Technically yes, because each provider maintains its own global listener state in its respective module. However, only one tunnel per provider type can be active at a time because the stop functions clear the global handle. To run both simultaneously, start each with its respective `start` function and manage their lifecycles independently via `stopCloudflaredTunnel` and `stopNgrokTunnel`.

### What happens if the tunnel process crashes while OmniRoute is running?

The tunnel modules are deliberately isolated from the main request pipeline. According to the OmniRoute source code, a crashed `cloudflared` or ngrok process will not terminate the LLM routing engine. The status endpoints will reflect the failure via the `error` field, allowing your application to detect the outage and attempt a restart without restarting the entire OmniRoute server.