Documentation Index
Fetch the complete documentation index at: https://rustunnel.com/docs/llms.txt
Use this file to discover all available pages before exploring further.
This is the complete reference for the rustunnel CLI — every flag, command, and config option. 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
From source (recommended)
Requires Rust 1.75 or later.
git clone https://github.com/joaoh82/rustunnel
cd rustunnel
cargo build --release -p rustunnel-client
sudo install -Dm755 target/release/rustunnel /usr/local/bin/rustunnel
Or use the Makefile shortcut:
Verify
Quick Start
# 1. Get an auth token
# → Sign up at https://rustunnel.com, then go to Dashboard → API Keys → Create token
# 2. Create a config file interactively
rustunnel setup
# → prompts for region and auth token, writes ~/.rustunnel/config.yml
# 3. Expose a local web server running on port 3000
rustunnel http 3000
# 4. 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 — choose a region)
server: eu.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
| Field | Type | Default | Description |
|---|
server | string | — | Tunnel server host:port (e.g. eu.edge.rustunnel.com:4040) |
auth_token | string | — | Authentication token issued by the server |
insecure | bool | false | Skip TLS certificate verification (dev only) |
region | string | omit | auto (probe nearest), eu, us, ap. Omit for self-hosted setups. |
tunnels | map | {} | Named tunnel definitions (used by rustunnel start) |
tunnels.<name>.proto | string | — | http or tcp |
tunnels.<name>.local_port | integer | — | Local port to forward |
tunnels.<name>.local_host | string | localhost | Local hostname to connect to |
tunnels.<name>.subdomain | string | auto-assigned | Requested HTTP subdomain |
Commands
setup — Interactive config wizard
Create (or overwrite) ~/.rustunnel/config.yml through a guided prompt sequence.
Prompts:
| Prompt | Default | Description |
|---|
| Region | auto | auto (probe nearest), eu, us, ap, or self-hosted |
| Auth token | (blank) | Token issued by the server; leave empty to fill in later |
| Server address | (auto-resolved) | Only prompted when self-hosted is selected |
Region behavior:
| Choice | Server resolution |
|---|
eu / us / ap | Auto-set to <region>.edge.rustunnel.com:4040 |
auto | Probes all regions, picks the nearest by latency |
self-hosted | Prompts for your server address |
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):
rustunnel setup — create ~/.rustunnel/config.yml
Region [auto / eu / us / ap / self-hosted] (default: auto):
Selecting nearest region… eu 12ms · us 143ms · ap 311ms · → eu (Helsinki, FI) 12ms
Server set to: eu.edge.rustunnel.com:4040
Auth token (leave blank to skip): rt_live_abc123xyz
Created: /Users/alice/.rustunnel/config.yml
Run `rustunnel start` to connect using this config.
Example session (self-hosted):
rustunnel setup — create ~/.rustunnel/config.yml
Region [auto / eu / us / ap / self-hosted] (default: auto): self-hosted
Tunnel server address: tunnel.internal.corp:4040
Auth token (leave blank to skip): my-token
Created: /Users/alice/.rustunnel/config.yml
Run `rustunnel start` to connect using this config.
Generated file (managed):
# rustunnel configuration
# Documentation: https://github.com/joaoh82/rustunnel
server: eu.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
Generated file (self-hosted):
# rustunnel configuration
# Documentation: https://github.com/joaoh82/rustunnel
server: tunnel.internal.corp:4040
auth_token: my-token
# region: not applicable (self-hosted)
# tunnels:
# ...
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:
| Argument | Description |
|---|
<port> | Local TCP port to forward (e.g. 3000) |
Options:
| Flag | Default | Description |
|---|
--subdomain <name> | auto-assigned | Request a specific subdomain (e.g. myapp → myapp.eu.edge.rustunnel.com) |
--server <host:port> | from config | Override the server address (bypasses region selection) |
--token <token> | from config | Override the auth token |
--local-host <host> | localhost | Local hostname to forward to |
--region <id> | from config | Region to connect to: eu, us, ap, or auto. Ignored if --server is set. |
--no-reconnect | off | Exit instead of reconnecting on failure |
--insecure | off | Skip 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:
| Argument | Description |
|---|
<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:
| Flag | Default | Description |
|---|
-c, --config <path> | ~/.rustunnel/config.yml | Path 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.
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:
server: tunnel.example.com:4040
auth_token: "your-token"
tunnels:
a:
proto: http
local_port: 3000
subdomain: pool
group: web
group_key: shared-secret-for-this-pool
health_check:
type: tcp
# Optional: when this group goes 0/N healthy, the server POSTs a
# group_zero_healthy alert to this URL. Per-tenant — independent
# of any operator-side webhook configured on the edge.
alert_webhook: "https://hooks.slack.com/services/my-team/..."
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.
Build the client from source
git clone https://github.com/joaoh82/rustunnel
cd rustunnel
cargo build --release -p rustunnel-client
Drop a config that opts into a group
cat > /tmp/lb-test.yml <<'EOF'
server: tunnel.example.com:4040
auth_token: "your-token"
tunnels:
a:
proto: http
local_port: 3000
subdomain: lbtest
group: web
group_key: shared-secret-for-lb-test
health_check:
type: tcp
EOF
Start backend A and client A
# Terminal 1 — backend A
python3 -m http.server 3000
# Terminal 2 — client A
./target/release/rustunnel start --config /tmp/lb-test.yml
Start backend B and client B
Use a second config file that’s identical except for local_port: 3001.# Terminal 3 — backend B
python3 -m http.server 3001
# Terminal 4 — client B
./target/release/rustunnel start --config /tmp/lb-test-b.yml
Hammer the public URL
for i in $(seq 1 50); do
curl -fsS https://lbtest.tunnel.example.com/ -o /dev/null -w "%{http_code}\n"
done
Both backends should see roughly half of the requests.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.
rustunnel token create --name <label> [options]
Options:
| Flag | Default | Description |
|---|
--name <label> | — | Human-readable label for the token (required) |
--server <host:port> | localhost:4040 | Dashboard 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
| Flag | Commands | Description |
|---|
--server <host:port> | http, tcp | Tunnel server address (bypasses region selection) |
--token <token> | http, tcp | Auth token (overrides config) |
--subdomain <name> | http | Requested HTTP subdomain |
--local-host <host> | http, tcp | Local hostname (default: localhost) |
--region <id> | http, tcp | Region: eu, us, ap, or auto. Ignored if --server is set. |
--no-reconnect | http, tcp | Exit on failure instead of reconnecting |
--insecure | http, tcp | Skip TLS certificate verification |
-c, --config <path> | start | Config file path |
--name <label> | token create | Token label (required) |
--admin-token <token> | token create | Admin token for dashboard API |
--version | all | Print version and exit |
--help | all | Print 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:
--server <host:port> — explicit server address always wins; region logic is skipped entirely.
--region <id> — connect directly to the named region without probing.
region: auto (config file or --region auto) — probe all regions in parallel and pick the nearest.
- No region preference — use
server: from config as-is (backward compatible with self-hosted setups).
Available regions (hosted service)
| Region ID | Location | Server |
|---|
eu | Helsinki, FI | eu.edge.rustunnel.com:4040 |
us | Hillsboro, OR | us.edge.rustunnel.com:4040 |
ap | Singapore | ap.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:
| Attempt | Delay |
|---|
| 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: 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:
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
| Variable | Description |
|---|
RUST_LOG | Log 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
| Error | Cause | Fix |
|---|
config error: server address is required | No --server flag and no config file | Add server: to ~/.rustunnel/config.yml or pass --server |
auth failed: <message> | Token invalid or revoked | Create a new token at rustunnel.com (Dashboard → API Keys) or with rustunnel token create for self-hosted setups |
tunnel error: <message> | Subdomain already in use or server limit reached | Use a different --subdomain or wait |
connection error: control WS: … | Can’t reach the server | Check network, firewall, and server address |
connection error: heartbeat timeout | Server stopped responding to pings | Transient — reconnect loop will retry |
connection error: timeout waiting for server response | Auth/registration timed out (10 s) | Check server health; may be overloaded |
no tunnels defined in config file | rustunnel start with an empty tunnels: map | Add 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:
| Message | Meaning |
|---|
authenticated session_id=... | Auth succeeded |
tunnel registered public_url=... | Tunnel is active |
data WebSocket connected | Data plane is ready |
new connection from server conn_id=... | Incoming proxied request |
yamux data conn error | Data-plane transport error |
heartbeat timeout | Server 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