Skip to main content

Installation

Requires Rust 1.75 or later.
git clone https://github.com/your-org/rustunnel
cd rustunnel
cargo build --release -p rustunnel-client
sudo install -Dm755 target/release/rustunnel /usr/local/bin/rustunnel
Or use the Makefile shortcut:
make deploy-client

Verify

rustunnel --version

Quick Start

# 1. Create a config file interactively
rustunnel setup
# → prompts for server address and auth token, writes ~/.rustunnel/config.yml

# 2. Expose a local web server running on port 3000
rustunnel http 3000

# 3. Expose a raw TCP service (e.g. SSH on port 22)
rustunnel tcp 22
After connecting, the terminal displays the public URL:
╭────────────────────────────────────────────────────────────╮
│                         rustunnel                          │
├────────────────────────────────────────────────────────────┤
│  HTTP [myapp] → localhost:3000                             │
│   https://myapp.tunnel.example.com                        │
╰────────────────────────────────────────────────────────────╯

  ✓ Tunnels active. Press Ctrl-C to quit.

Configuration File

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

Full example

# Tunnel server address (required)
server: edge.rustunnel.com:4040

# Authentication token (required)
auth_token: rt_live_abc123...

# Skip TLS certificate verification — local dev ONLY, never use in production
insecure: false

# Region preference: auto (probe & pick nearest), or eu / us / ap.
# Omit for self-hosted / single-server setups.
region: auto

# Named tunnel definitions used by `rustunnel start`
tunnels:
  web:
    proto: http
    local_port: 3000
    local_host: localhost       # optional, defaults to localhost
    subdomain: myapp            # optional, requests a specific subdomain

  api:
    proto: http
    local_port: 8080
    subdomain: myapi

  database:
    proto: tcp
    local_port: 5432

Field reference

FieldTypeDefaultDescription
serverstringTunnel server host:port (e.g. edge.rustunnel.com:4040)
auth_tokenstringAuthentication token issued by the server
insecureboolfalseSkip TLS certificate verification (dev only)
regionstringomitauto (probe nearest), eu, us, ap. Omit for self-hosted setups.
tunnelsmap{}Named tunnel definitions (used by rustunnel start)
tunnels.<name>.protostringhttp or tcp
tunnels.<name>.local_portintegerLocal port to forward
tunnels.<name>.local_hoststringlocalhostLocal hostname to connect to
tunnels.<name>.subdomainstringauto-assignedRequested HTTP subdomain

Commands

setup — Interactive config wizard

Create (or overwrite) ~/.rustunnel/config.yml through a guided prompt sequence.
rustunnel setup
Prompts:
PromptDefaultDescription
Server addressedge.rustunnel.com:4040The control-plane host:port to connect to
Auth token(blank)Token issued by the server; leave empty to fill in later
Regionautoauto (probe nearest), eu, us, or ap
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:
rustunnel setup — create ~/.rustunnel/config.yml

Tunnel server address [edge.rustunnel.com:4040]:
Auth token (leave blank to skip): rt_live_abc123xyz
Region [auto / eu / us / ap] (default: auto):

Created: /Users/alice/.rustunnel/config.yml
Run `rustunnel start` to connect using this config.
Generated file:
# rustunnel configuration
# Documentation: https://github.com/joaoh82/rustunnel

server: edge.rustunnel.com:4040
auth_token: rt_live_abc123xyz
region: auto

# tunnels:
#   web:
#     proto: http
#     local_port: 3000
#   api:
#     proto: http
#     local_port: 8080
#     subdomain: myapi
#   database:
#     proto: tcp
#     local_port: 5432
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.
rustunnel http <port> [options]
Arguments:
ArgumentDescription
<port>Local TCP port to forward (e.g. 3000)
Options:
FlagDefaultDescription
--subdomain <name>auto-assignedRequest a specific subdomain (e.g. myappmyapp.eu.edge.rustunnel.com)
--server <host:port>from configOverride the server address (bypasses region selection)
--token <token>from configOverride the auth token
--local-host <host>localhostLocal hostname to forward to
--region <id>from configRegion to connect to: eu, us, ap, or auto. Ignored if --server is set.
--no-reconnectoffExit instead of reconnecting on failure
--insecureoffSkip TLS verification (dev only)
Examples:
# Expose port 3000 — auto-selects the nearest region
rustunnel http 3000

# Connect to a specific region
rustunnel http 3000 --region eu

# Request a specific subdomain
rustunnel http 3000 --subdomain myapp

# Forward to a non-localhost service
rustunnel http 8080 --local-host 192.168.1.10

# One-shot connection (exit on disconnect instead of reconnecting)
rustunnel http 3000 --no-reconnect

# Use an explicit server address (bypasses region selection)
rustunnel http 3000 --server tunnel.example.com:9000 --token rt_live_abc123

tcp — TCP tunnel

Expose any raw TCP service (database, SSH, game server, etc.).
rustunnel tcp <port> [options]
Arguments:
ArgumentDescription
<port>Local TCP port to forward
Options: Same as http except --subdomain has no effect for TCP tunnels. --region still works. Examples:
# Expose a local PostgreSQL instance
rustunnel tcp 5432

# Expose SSH on a non-standard port
rustunnel tcp 2222 --local-host 10.0.0.5
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.
rustunnel start [--config <path>]
Options:
FlagDefaultDescription
-c, --config <path>~/.rustunnel/config.ymlPath to a config file
Example:
# Use default config file
rustunnel start

# Use a custom config file
rustunnel start --config /etc/rustunnel/production.yml
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.

token create — API token management

Create a new API token via the server’s dashboard REST API. Requires admin credentials.
rustunnel token create --name <label> [options]
Options:
FlagDefaultDescription
--name <label>Human-readable label for the token (required)
--server <host:port>localhost:4040Dashboard server address
--admin-token <token>Admin token for authentication
Example:
rustunnel token create \
  --name "production-server" \
  --server tunnel.example.com:4040 \
  --admin-token admin_secret_here
Output:
Token created:
  id:    f47ac10b-58cc-4372-a567-0e02b2c3d479
  token: rt_live_abc123xyz...
  label: production-server
Copy the token value — it is shown only once. Add it to your config file as auth_token.

Flags Reference

FlagCommandsDescription
--server <host:port>http, tcpTunnel server address (bypasses region selection)
--token <token>http, tcpAuth token (overrides config)
--subdomain <name>httpRequested HTTP subdomain
--local-host <host>http, tcpLocal hostname (default: localhost)
--region <id>http, tcpRegion: eu, us, ap, or auto. Ignored if --server is set.
--no-reconnecthttp, tcpExit on failure instead of reconnecting
--insecurehttp, tcpSkip TLS certificate verification
-c, --config <path>startConfig file path
--name <label>token createToken label (required)
--admin-token <token>token createAdmin token for dashboard API
--versionallPrint version and exit
--helpallPrint help and exit
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)

Region IDLocationServer
euHelsinki, FIeu.edge.rustunnel.com:4040
usHillsboro, ORus.edge.rustunnel.com:4040
apSingaporeap.edge.rustunnel.com:4040

Auto-select output

When region: auto is active, the client probes all regions by TCP connect time and prints:
  Selecting nearest region… eu 12ms · us 143ms · ap 311ms → eu (Helsinki, FI) 12ms
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:
AttemptDelay
1~1 s
2~2 s
3~4 s
4~8 s
n≥6~60 s (max)
Each delay has ±20% random jitter to prevent thundering-herd reconnects when a server restarts.
  Reconnecting in 2.3s (attempt 2)…
  Reconnecting in 5.1s (attempt 3)…

Fatal errors (no reconnect)

The following errors cause an immediate exit — retrying would not help:
  • Auth failed — invalid or revoked token. Fix: run rustunnel token create 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:
rustunnel http 3000 --no-reconnect || echo "Tunnel exited"

Terminal Output

Connecting spinner

While establishing the connection a spinner is shown:
⠙ Connecting to tunnel server…
⠹ Authenticating…
⠸ Registering tunnels…

Startup box

Once all tunnels are registered, a bordered box appears:
╭────────────────────────────────────────────────────────────╮
│                         rustunnel                          │
├────────────────────────────────────────────────────────────┤
│   HTTP [myapp] → localhost:3000                            │
│   https://myapp.tunnel.example.com                        │
│   TCP  [ssh]   → localhost:22                             │
│   tcp://tunnel.example.com:34521                          │
╰────────────────────────────────────────────────────────────╯

  ✓ Tunnels active. Press Ctrl-C to quit.
Color coding:
  • Protocol label — bold yellow
  • Tunnel name — dim
  • Public URL — bold green
  • Border — cyan

Graceful shutdown

Press Ctrl-C to cleanly close the tunnel and exit. The control WebSocket is closed before the process exits.

Environment Variables

VariableDescription
RUST_LOGLog level filter (e.g. debug, info, warn, rustunnel=debug). Default: warn.
Examples:
# Enable debug logging for all crates
RUST_LOG=debug rustunnel http 3000

# Enable debug only for rustunnel internals
RUST_LOG=rustunnel=debug rustunnel http 3000

# Quiet mode (errors only)
RUST_LOG=error rustunnel http 3000
Log output goes to stderr. Normal tunnel output (startup box, reconnect messages) goes to stdout.

Error Reference

ErrorCauseFix
config error: server address is requiredNo --server flag and no config fileAdd server: to ~/.rustunnel/config.yml or pass --server
auth failed: <message>Token invalid or revokedCreate a new token with rustunnel token create
tunnel error: <message>Subdomain already in use or server limit reachedUse a different --subdomain or wait
connection error: control WS: …Can’t reach the serverCheck network, firewall, and server address
connection error: heartbeat timeoutServer stopped responding to pingsTransient — reconnect loop will retry
connection error: timeout waiting for server responseAuth/registration timed out (10 s)Check server health; may be overloaded
no tunnels defined in config filerustunnel start with an empty tunnels: mapAdd at least one tunnel to the config

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:
rustunnel http 3000 --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:
RUST_LOG=debug rustunnel http 3000 2>&1 | tee rustunnel.log
Key log messages to look for:
MessageMeaning
authenticated session_id=...Auth succeeded
tunnel registered public_url=...Tunnel is active
data WebSocket connectedData plane is ready
new connection from server conn_id=...Incoming proxied request
yamux data conn errorData-plane transport error
heartbeat timeoutServer stopped responding — will reconnect

Multiple tunnels on the same server

Use rustunnel start with a config file to open all tunnels over a single control connection:
server: tunnel.example.com:9000
auth_token: rt_live_abc123

tunnels:
  frontend:
    proto: http
    local_port: 3000
    subdomain: app
  backend:
    proto: http
    local_port: 8080
    subdomain: api
  metrics:
    proto: tcp
    local_port: 9090
rustunnel start