Skip to main content
This is the complete reference for the rustunnel CLI — every flag, command, and config option, including the live terminal UI and request inspector shipping in 0.8.3. New to rustunnel? Start with the Quickstart for a three-step walkthrough, or the self-hosting guide if you want to run your own server.

Installation

Requires Rust 1.75 or later.
Or use the Makefile shortcut:

Verify


Quick Start

After connecting, the terminal displays the public URL:

Configuration File

The client reads ~/.rustunnel/config.yml automatically. CLI flags always override file values.

Full example

Field reference


Commands

setup — Interactive config wizard

Create (or overwrite) ~/.rustunnel/config.yml through a guided prompt sequence.
Prompts: Region behavior: Behaviour:
  • Creates ~/.rustunnel/ if the directory doesn’t exist.
  • If a config file already exists it is overwritten — a backup is not kept, so copy the old file first if you want to preserve it.
  • Writes a commented tunnels: block with HTTP and TCP examples so you can see the structure right away.
  • Prints Created: or Updated: with the full path when done.
Example session (auto region):
Example session (self-hosted):
Generated file (managed):
Generated file (self-hosted):
After running setup, uncomment and fill in the tunnels: section then run rustunnel start, or use rustunnel http <port> / rustunnel tcp <port> directly.

http — HTTP tunnel

Expose a local HTTP/HTTPS service through the tunnel server.
Arguments: Options: Examples:

tcp — TCP tunnel

Expose any raw TCP service (database, SSH, game server, etc.).
Arguments: Options: Same as http except --subdomain has no effect for TCP tunnels. --region still works. Examples:
The server assigns a random public port from its configured TCP port range. The public address is displayed in the startup box.

start — Multi-tunnel mode

Start all tunnels defined in a config file simultaneously.
Options: Example:
start always reconnects automatically (equivalent to running each tunnel without --no-reconnect). At least one tunnel must be defined in the config file or the command exits with an error.

Multiple backends (load balancing)

When start is given two or more tunnels with the same (group, group_key), the server treats them as one logical pool — inbound traffic is dispatched to a healthy member at random. This is the path for running a hot-spare backend, scaling out a service horizontally behind a single subdomain, or doing zero-downtime rollouts. Add the load-balancing fields to each pool member’s tunnel definition:
Run a second client (on the same or a different host) with local_port: 3001, the same subdomain, and the same (group, group_key). Both clients register as members of the same pool; public connections to pool.tunnel.example.com are dispatched at random across them.
Load balancing requires [load_balancing] enabled = true in the server’s server.toml. When the flag is false (the default), the client’s group / group_key / health_check fields are accepted but ignored — both clients try to register as solo tunnels and the second one fails with subdomain '...' is already in use.

Quick smoke test

End-to-end validation with two backends sharing one subdomain.
1

Build the client from source

2

Drop a config that opts into a group

3

Start backend A and client A

4

Start backend B and client B

Use a second config file that’s identical except for local_port: 3001.
5

Hammer the public URL

Both backends should see roughly half of the requests.
6

Validate failover

Kill one of the local backends (Ctrl-C on its python3 process). The probe loop on that client marks it unhealthy after max_failed × interval_secs seconds; subsequent requests all land on the survivor. Restart the backend — the probe re-registers it as healthy and dispatch distributes again.
For the full reference (config field meanings, behaviour rules, observability series, limitations) see Load Balancing & Health Checks.

token create — API token management

Create a new API token via the server’s dashboard REST API. Requires admin credentials.
Options: Example:
Output:
Copy the token value — it is shown only once. Add it to your config file as auth_token.

Flags Reference

setup takes no flags — all input is collected interactively.

Region Selection

rustunnel can connect to multiple edge servers in different geographic regions. The region selection logic follows this priority order:
  1. --server <host:port> — explicit server address always wins; region logic is skipped entirely.
  2. --region <id> — connect directly to the named region without probing.
  3. region: auto (config file or --region auto) — probe all regions in parallel and pick the nearest.
  4. No region preference — use server: from config as-is (backward compatible with self-hosted setups).

Available regions (hosted service)

Auto-select output

When region: auto is active, the client probes all regions by TCP connect time and prints:
Unreachable regions time out after 3 seconds and are assigned a 10-second penalty so they never win the selection.

Region list refresh

The region list is cached at ~/.rustunnel/regions.json for 24 hours. On expiry the client fetches a fresh list from GET https://<host>:8443/api/regions; if that fails it falls back to the hardcoded list compiled into the binary.

Reconnection Behavior

By default, rustunnel reconnects automatically when the connection drops. The retry delay follows an exponential backoff schedule: Each delay has ±20% random jitter to prevent thundering-herd reconnects when a server restarts.

Fatal errors (no reconnect)

The following errors cause an immediate exit — retrying would not help:
  • Auth failed — invalid or revoked token. Fix: create a new token at rustunnel.com (Dashboard → API Keys) and update your config.
  • Config error — missing required fields. Fix: check your ~/.rustunnel/config.yml.

Disabling reconnect

Use --no-reconnect for scripting, CI, or when you want manual control:

Terminal Output

When stdout is a terminal, the client takes over the screen with a live UI. Otherwise — piped output, CI, --no-tui, or --json — it falls back to the line-based output described further down.

Terminal UI

rustunnel full-screen terminal UI showing session status, tunnel health, traffic counters, and request log

Live terminal UI — session status, tunnels, traffic counters, and a scrolling request log

  • Session — connection state, uptime, client version, edge server, region and live control-plane latency (measured from the keepalive round-trip), and the local inspector URL.
  • Tunnels — one row per tunnel with its public URL, local target, and health when a health_check is configured.
  • Traffic — open/total connections, bytes each way, p50/p90 request duration, and a requests-per-second sparkline.
  • Requests — live log of every HTTP request: time, method, path, status, duration, and the public client address. Replayed requests are marked .
Keys: Only HTTP tunnels produce request rows. TCP and UDP tunnels are opaque byte streams, so they show connection and traffic counters only.

Line mode

With --no-tui, a non-terminal stdout, or --json, output stays line-based. While establishing the connection a spinner is shown:
Once all tunnels are registered, a bordered box appears:
Color coding:
  • Protocol label — bold yellow
  • Tunnel name — dim
  • Public URL — bold green
  • Border — cyan
Requests then stream one per line as they arrive:

JSON output (--json)

With --json, the spinner and startup box are suppressed and stdout instead carries NDJSON — one JSON event object per line — for scripts and AI agents:
Events: inspector_ready (url; emitted once at startup when the request inspector is enabled), tunnel_ready (includes public_addr host:port for tcp/udp), reconnecting (attempt, reason, delay_secs), reconnected, error (code, message, hint; exit code 1 follows), and token_created (for token create --json). Diagnostics still go to stderr. A --json session that also runs the inspector emits it first, so a script can pick the port up before any traffic arrives:

Graceful shutdown

Press Ctrl-C to cleanly close the tunnel and exit. The control WebSocket is closed before the process exits. In the terminal UI, q and Esc do the same.

Request Inspector

Every tunnel session also starts a small web inspector on loopback, printed at startup and shown in the terminal UI:
Open it to browse everything that flowed through the tunnel. See the dedicated Request Inspector guide for the full walkthrough.
rustunnel local web request inspector showing request list, detail tabs, and Replay button

Local request inspector at http://127.0.0.1:4040 — request list and detail with Replay

  • Request list — live-updating, with method, path, status, and duration. Filter by method, path, or status.
  • Detail view — Summary (bodies), Headers (full request and response headers), and Raw (the reconstructed HTTP messages).
  • Replay — re-issue any captured request against your local service without the original caller doing anything. Handy for webhooks: trigger once, then iterate against the same payload. Replayed requests appear in the list marked replay.

Scope and limits

  • Captures HTTP tunnels only. TCP and UDP tunnels are raw byte streams; they contribute connection and traffic counters but no request entries.
  • Keeps the last 500 requests in memory, per process. Nothing is written to disk and nothing survives a restart.
  • Bodies are captured up to 64 KB each; larger ones are marked truncated (the reported size is still exact). A truncated request body cannot be replayed byte-for-byte.
  • WebSocket and other upgraded connections are recorded as their handshake (101); the frames afterwards are not parsed.

Configuration

Because the port can shift when the preferred one is taken, always read the actual URL rather than assuming 4040 — from the terminal UI header, the line under the startup box, or the inspector_ready event in --json mode. The inspector binds 127.0.0.1 only and has no authentication, so treat it as local-only — captured payloads may contain credentials, tokens, and personal data. Use --no-inspect on shared or multi-user machines.

Environment Variables

Examples:
Log output goes to stderr. Normal tunnel output (terminal UI / line mode, reconnect messages) goes to stdout.

Error Reference


Troubleshooting

Tunnel connects but requests don’t arrive

  • Verify your local service is running and listening: curl http://localhost:<port>
  • Check --local-host if forwarding to a non-localhost address

Certificate verification failed

If your server uses a self-signed certificate (common for local/staging environments), use --insecure:
Never use --insecure in production — it disables all TLS certificate checks.

Subdomain already taken

The server returns tunnel error: subdomain already in use. Either:
  • Omit --subdomain to get an auto-assigned subdomain, or
  • Choose a different name: --subdomain myapp-dev

Debugging connection issues

Enable verbose logging to see full protocol traces:
Key log messages to look for:

Multiple tunnels on the same server

Use rustunnel start with a config file to open all tunnels over a single control connection:

Quickstart

Expose your first local port in three steps with the hosted service.

Request Inspector

Browse and replay HTTP traffic on localhost:4040 while a tunnel is live.

Self-Hosting Guide

Run your own rustunnel server with systemd, TLS, and PostgreSQL.

Load Balancing & Health Checks

Pool multiple backends behind one subdomain with automatic failover.