https://github.com/joaoh82/rustunnel
**Rustunnel** is a open-source tunnel service written in Rust that replicates the core functionality of ngrok. It exposes local services running behind NAT/firewalls to the public internet through a relay server self-hosted or our managed service.
https://github.com/joaoh82/rustunnel
api-gateway reverse-proxy rust rustunnel
Last synced: 18 days ago
JSON representation
**Rustunnel** is a open-source tunnel service written in Rust that replicates the core functionality of ngrok. It exposes local services running behind NAT/firewalls to the public internet through a relay server self-hosted or our managed service.
- Host: GitHub
- URL: https://github.com/joaoh82/rustunnel
- Owner: joaoh82
- License: agpl-3.0
- Created: 2026-03-10T21:25:25.000Z (2 months ago)
- Default Branch: main
- Last Pushed: 2026-03-28T23:07:34.000Z (about 2 months ago)
- Last Synced: 2026-03-29T00:55:41.386Z (about 2 months ago)
- Topics: api-gateway, reverse-proxy, rust, rustunnel
- Language: Rust
- Homepage: https://www.rustunnel.com
- Size: 8.55 MB
- Stars: 593
- Watchers: 2
- Forks: 40
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
- Roadmap: docs/ROADMAP.md
- Maintainers: MAINTAINERS
- Copyright: COPYRIGHT
Awesome Lists containing this project
README
# rustunnel
[](https://github.com/joaoh82/rustunnel/actions/workflows/ci.yml)
[](LICENSE)
[](https://www.rust-lang.org)

The open-source tunnel that scales with you. Don't pay for idle time. Secure, Rust-fast, and Pay-as-you-go.
Expose local services through a public server over encrypted WebSocket connections with TLS termination, HTTP/TCP proxying, a live dashboard, Prometheus metrics, and audit logging.
You can self-host or use our managed service.
---
## Table of Contents
- [Hosted service](#hosted-service)
- [Architecture overview](#architecture-overview)
- [Requirements](#requirements)
- [Local development setup](#local-development-setup)
- [Build](#build)
- [Run tests](#run-tests)
- [Run the server locally](#run-the-server-locally)
- [Run the client locally](#run-the-client-locally)
- [Git hooks](#git-hooks)
- [Production deployment (Ubuntu / systemd)](#production-deployment-ubuntu--systemd)
- [1 — Install dependencies](#1--install-dependencies)
- [2 — Build release binaries](#2--build-release-binaries)
- [3 — Create system user and directories](#3--create-system-user-and-directories)
- [4 — Install the server binary](#4--install-the-server-binary)
- [5 — Create the server config file](#5--create-the-server-config-file)
- [6 — TLS certificates (Let's Encrypt + Cloudflare)](#6--tls-certificates-lets-encrypt--cloudflare)
- [7 — Set up systemd service](#7--set-up-systemd-service)
- [8 — Open firewall ports](#8--open-firewall-ports)
- [9 — Verify the server is running](#9--verify-the-server-is-running)
- [Updating the server](#updating-the-server)
- [Docker deployment](#docker-deployment) · [full guide](docs/docker-deployment.md)
- [Client configuration](#client-configuration)
- [Installation](#installation)
- [Setup wizard](#setup-wizard)
- [Quick start (CLI flags)](#quick-start-cli-flags)
- [Config file](#config-file)
- [Token management](#token-management)
- [Port reference](#port-reference)
- [Config file reference (server)](#config-file-reference-server)
- [REST API](#rest-api)
- [AI agent integration (MCP server)](#ai-agent-integration-mcp-server)
- [OpenClaw skill](#openclaw-skill)
- [Monitoring](#monitoring)
- [Roadmap](#roadmap)
- [Contributing](#contributing)
- [License](#license)
- [Contact](#contact)
---
## Hosted service
You can use rustunnel without running your own server. We operate a global fleet of public edge servers that you can connect to immediately.
### Available regions
| Region ID | Server | Location | Control plane | Status |
|-----------|--------|----------|---------------|--------|
| `eu` | `eu.edge.rustunnel.com` | Helsinki, FI | `:4040` | Live |
| `us` | `us.edge.rustunnel.com` | Hillsboro, OR | `:4040` | Live |
| `ap` | `ap.edge.rustunnel.com` | Singapore | `:4040` | Live |
The client auto-selects the nearest region by default. Use `--region ` to connect to a specific one. The legacy address `edge.rustunnel.com` is a CNAME to `eu.edge.rustunnel.com` and will continue to work for backward compatibility.
### Getting an auth token
Sign up for a free account at **[rustunnel.com](https://rustunnel.com)** — no waiting list, no manual approval.
1. Create an account at [rustunnel.com](https://rustunnel.com)
2. Go to **Dashboard → API Keys** and create a token
3. Copy the token — it is shown only once
**Plans:**
| Plan | Price | Tunnels | Custom subdomains | TLS/HTTPS |
|------|-------|---------|-------------------|-----------|
| Free | $0 | Up to 3 | — | ✓ |
| Pay-as-you-go | $3/mo minimum + $0.10/GB | Unlimited | ✓ | ✓ |
| Self-host | Free (run your own server) | Unlimited | ✓ | ✓ |
The free plan is a great way to get started. Upgrade to pay-as-you-go from your dashboard whenever you need custom subdomains or unlimited tunnels.
### Quick start with the hosted server
Once you have a token, run the setup wizard:
```bash
rustunnel setup
# Region [auto / eu / us / ap / self-hosted] (default: auto): (press Enter)
# Selecting nearest region… eu 12ms · us 143ms · ap 311ms · → eu (Helsinki, FI) 12ms
# Server set to: eu.edge.rustunnel.com:4040
# Auth token:
```
Then expose a local service:
```bash
# HTTP tunnel — auto-selects the nearest region
rustunnel http 3000
# Connect to a specific region
rustunnel http 3000 --region eu
# Custom subdomain
rustunnel http 3000 --subdomain myapp
# TCP tunnel — e.g. expose a local database
rustunnel tcp 5432
# UDP tunnel — e.g. expose a game server
rustunnel udp 27015
# P2P tunnel — expose a service to another rustunnel client
rustunnel p2p 27015 --name my-game --secret "shared-secret"
# P2P tunnel — connect to a peer's service
rustunnel p2p 8000 --target my-game --secret "shared-secret"
```
The client prints the public URL as soon as the tunnel is established:
```
Selecting nearest region… eu 12ms · us 143ms · ap 311ms → eu (Helsinki, FI) 12ms
✓ tunnel open https://abc123.eu.edge.rustunnel.com
```
---
## Architecture overview

```
┌──────────────────────────────────────────┐
│ rustunnel-server │
│ │
Internet ──── :80 ─────▶│ HTTP edge (301 → HTTPS) │
Internet ──── :443 ────▶│ HTTPS edge ──▶ yamux stream ──▶ client │
Client ───── :4040 ────▶│ Control-plane WebSocket (TLS) │
Browser ──── :8443 ────▶│ Dashboard UI + REST API │
Prometheus ─ :9090 ────▶│ Metrics endpoint │
Internet ── :20000+ ───▶│ TCP tunnel ports (one per TCP tunnel) │
└──────────────────────────────────────────┘
│ yamux multiplexed streams
▼
┌─────────────────────┐
│ rustunnel client │
│ (developer laptop) │
└──────────┬──────────┘
│ localhost
▼
┌────────────────┐
│ local service │
│ e.g. :3000 │
└────────────────┘
```
---
## Requirements
### To build
| Requirement | Version | Notes |
|---|---|---|
| Rust toolchain | 1.76+ | Install via [rustup](https://rustup.rs) |
| `pkg-config` | any | Needed by `reqwest` (TLS) |
| `libssl-dev` | any | On Debian/Ubuntu: `apt install libssl-dev` |
| Node.js + npm | 18+ | Only needed to rebuild the dashboard UI |
### To run the server in production
| Requirement | Notes |
|---|---|
| Linux (Ubuntu 22.04+) | systemd service included |
| TLS certificate + private key | PEM format (Let's Encrypt recommended) |
| Public IP / DNS | Wildcard DNS `*.tunnel.yourdomain.com → server IP` required for HTTP tunnels |
---
## Local development setup
### Build
```bash
# Clone the repository
git clone https://github.com/joaoh82/rustunnel.git
cd rustunnel
# Compile all workspace crates (debug mode)
cargo build --workspace
# Or use the Makefile shortcut
make build
```
### Run tests
The integration test suite spins up a real server on random ports and exercises auth, HTTP tunnels, TCP tunnels, and reconnection logic. It requires a running PostgreSQL instance.
```bash
# Start the local PostgreSQL container (once per machine, persists across reboots)
make db-start
# Full suite (unit + integration)
make test
# With output visible
TEST_DATABASE_URL=postgres://rustunnel:test@localhost:5432/rustunnel_test \
cargo test --workspace -- --nocapture
# Stop PostgreSQL when you no longer need it
make db-stop
```
`make db-start` runs `deploy/docker-compose.dev-deps.yml` which starts a Postgres 16 container on `localhost:5432`. The `make test` target injects `TEST_DATABASE_URL` automatically. If you run `cargo test` directly, export the variable first:
```bash
export TEST_DATABASE_URL=postgres://rustunnel:test@localhost:5432/rustunnel_test
```
### Run the server locally
Generate a self-signed certificate for local testing:
```bash
mkdir -p /tmp/rustunnel-dev
openssl req -x509 -newkey rsa:2048 -keyout /tmp/rustunnel-dev/key.pem \
-out /tmp/rustunnel-dev/cert.pem -days 365 -nodes \
-subj "/CN=localhost"
```
A ready-made local config is checked into the repository at **`deploy/local/server.toml`**.
It points to the self-signed cert paths above and has auth disabled for convenience.
Start the server with it directly:
```bash
cargo run -p rustunnel-server -- --config deploy/local/server.toml
```
Key settings in `deploy/local/server.toml`:
| Setting | Value |
|---|---|
| Domain | `localhost` |
| HTTP edge | `:8080` |
| HTTPS edge | `:8443` |
| Control plane | `:4040` |
| Dashboard | `:4041` |
| Auth token | `dev-secret-change-me` |
| Auth required | `false` |
| TLS cert | `/tmp/rustunnel-dev/cert.pem` |
| TLS key | `/tmp/rustunnel-dev/key.pem` |
| Database | `/tmp/rustunnel-dev/rustunnel.db` |
### Run the client locally
With the server running, expose a local service (e.g. something on port 3000):
```bash
# HTTP tunnel
cargo run -p rustunnel-client -- http 3000 \
--server localhost:4040 \
--token dev-secret-change-me \
--insecure
# TCP tunnel
cargo run -p rustunnel-client -- tcp 5432 \
--server localhost:4040 \
--token dev-secret-change-me \
--insecure
```
> `--insecure` skips TLS certificate verification. Required when using a
> self-signed certificate locally. Never use this flag against a production server.
The client will print a public URL, for example:
```
http tunnel → http://abc123.localhost:8080
tcp tunnel → tcp://localhost:20000
```
### Testing the HTTP tunnel locally
The tunnel URL uses a subdomain (e.g. `http://abc123.localhost:8080`).
Browsers won't resolve `*.localhost` subdomains by default, so you have two options:
**Option A — curl with a Host header (no setup required)**
```bash
curl -v -H "Host: abc123.localhost" http://localhost:8080/
```
**Option B — wildcard DNS via dnsmasq (enables browser access)**
```bash
# Install and configure dnsmasq to resolve *.localhost → 127.0.0.1
brew install dnsmasq
echo "address=/.localhost/127.0.0.1" | sudo tee -a $(brew --prefix)/etc/dnsmasq.conf
sudo brew services start dnsmasq
# Tell macOS to use dnsmasq for .localhost queries
sudo mkdir -p /etc/resolver
echo "nameserver 127.0.0.1" | sudo tee /etc/resolver/localhost
```
Then visit `http://abc123.localhost:8080` in the browser (include `:8080` since the
local config uses port 8080, not port 80).
### Git hooks
A pre-push hook is included in `.githooks/` that mirrors the CI check step
(format check + Clippy). Run this once after cloning to activate it:
```bash
make install-hooks
```
From that point on, every `git push` will automatically run:
```bash
cargo fmt --all -- --check
cargo clippy --workspace --all-targets -- -D warnings
```
If either check fails the push is aborted, keeping the remote branch green.
---
## Production deployment (Ubuntu / systemd)
The steps below match a deployment where:
- Domain: `edge.rustunnel.com`
- Wildcard DNS: `*.edge.rustunnel.com → `
- TLS certs: Let's Encrypt via Certbot + Cloudflare DNS challenge
### 1 — Install dependencies
```bash
apt update && apt install -y \
pkg-config libssl-dev curl git \
certbot python3-certbot-dns-cloudflare
```
Install Rust (as the build user, not root):
```bash
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source "$HOME/.cargo/env"
```
### 2 — Build release binaries
```bash
git clone https://github.com/joaoh82/rustunnel.git
cd rustunnel
cargo build --release -p rustunnel-server -p rustunnel-client
```
Binaries will be at:
- `target/release/rustunnel-server`
- `target/release/rustunnel`
### 3 — Create system user and directories
```bash
useradd --system --no-create-home --shell /usr/sbin/nologin rustunnel
mkdir -p /etc/rustunnel /var/lib/rustunnel
chown rustunnel:rustunnel /var/lib/rustunnel
chmod 750 /var/lib/rustunnel
```
### 4 — Install the server binary
```bash
install -Dm755 target/release/rustunnel-server /usr/local/bin/rustunnel-server
# Optionally install the client system-wide
install -Dm755 target/release/rustunnel /usr/local/bin/rustunnel
```
Or use the Makefile target (runs build + install + systemd setup):
```bash
sudo make deploy
```
### 5 — Set up PostgreSQL
rustunnel requires PostgreSQL for shared state (tokens, tunnel history, audit log).
```bash
apt install -y postgresql postgresql-contrib
# Start and enable the service
systemctl enable --now postgresql
```
Create a dedicated database and user:
```bash
sudo -u postgres psql <<'SQL'
CREATE USER rustunnel WITH PASSWORD 'CHANGE_ME';
CREATE DATABASE rustunnel OWNER rustunnel;
GRANT ALL PRIVILEGES ON DATABASE rustunnel TO rustunnel;
SQL
```
> **Tip:** For managed PostgreSQL (e.g. AWS RDS, DigitalOcean Managed Database, Supabase) skip the
> `apt install` step above and just note the connection URL for the config in the next step.
Schema migrations run automatically when the server starts — no manual SQL needed.
### 6 — Create the server config file
Create `/etc/rustunnel/server.toml` with the content below.
Replace `your-admin-token-here` with a strong random secret (e.g. `openssl rand -hex 32`).
```toml
# /etc/rustunnel/server.toml
[server]
# Primary domain — must match your wildcard DNS record.
domain = "edge.rustunnel.com"
# Ports for incoming tunnel traffic (requires CAP_NET_BIND_SERVICE or root).
http_port = 80
https_port = 443
# Control-plane WebSocket port — clients connect here.
control_port = 4040
# Dashboard UI and REST API port.
dashboard_port = 8443
# Allowed CORS origin for the dashboard UI.
# Set to the URL where you serve the dashboard-ui (e.g. http://localhost:3000 for local dev).
dashboard_origin = "http://localhost:3000"
# ── TLS ─────────────────────────────────────────────────────────────────────
[tls]
# Paths written by Certbot (see step 6).
cert_path = "/etc/letsencrypt/live/edge.rustunnel.com/fullchain.pem"
key_path = "/etc/letsencrypt/live/edge.rustunnel.com/privkey.pem"
# Set acme_enabled = true only if you want rustunnel to manage certs itself
# via the ACME protocol (requires Cloudflare credentials below).
# When using Certbot (recommended), leave this false.
acme_enabled = false
# ── Auth ─────────────────────────────────────────────────────────────────────
[auth]
# Strong random secret — used both as the admin token and for client auth.
# Generate: openssl rand -hex 32
admin_token = "your-admin-token-here"
require_auth = true
# ── Database ─────────────────────────────────────────────────────────────────
[database]
# PostgreSQL connection URL — the database and user must exist before starting
# the server (see the PostgreSQL setup step above). Schema migrations run
# automatically on first start.
url = "postgresql://rustunnel:CHANGE_ME@localhost:5432/rustunnel"
# Per-region SQLite file for captured HTTP request bodies.
# The directory must be writable by the rustunnel user.
captured_path = "/var/lib/rustunnel/captured.db"
# ── Logging ──────────────────────────────────────────────────────────────────
[logging]
level = "info"
format = "json"
# Optional: write an append-only audit log (JSON-lines) for auth attempts,
# tunnel registrations, token creation/deletion, and admin actions.
# Omit or comment out to disable.
audit_log_path = "/var/lib/rustunnel/audit.log"
# ── Limits ───────────────────────────────────────────────────────────────────
[limits]
# Maximum tunnels a single authenticated session may register.
max_tunnels_per_session = 10
# Maximum simultaneous proxied connections per tunnel (semaphore).
max_connections_per_tunnel = 100
# Per-tunnel request rate limit (requests/second).
rate_limit_rps = 100
# Per-source-IP rate limit (requests/second). Set to 0 to disable.
ip_rate_limit_rps = 100
# Maximum size of a proxied HTTP request body (bytes). Default: 10 MB.
request_body_max_bytes = 10485760
# Inclusive port range reserved for TCP tunnels.
# Each active TCP tunnel consumes one port from this range.
tcp_port_range = [20000, 20099]
# Inclusive port range reserved for UDP tunnels.
# Each active UDP tunnel consumes one port from this range.
# Must not overlap with tcp_port_range. Set to [0, 0] to disable UDP tunnels.
udp_port_range = [20100, 20199]
```
Secure the file:
```bash
chown root:rustunnel /etc/rustunnel/server.toml
chmod 640 /etc/rustunnel/server.toml
```
### 7 — TLS certificates (Let's Encrypt + Cloudflare)
Create the Cloudflare credentials file:
```bash
cat > /etc/letsencrypt/cloudflare.ini <<'EOF'
# Cloudflare API token with DNS:Edit permission for the zone.
dns_cloudflare_api_token = YOUR_CLOUDFLARE_API_TOKEN
EOF
chmod 600 /etc/letsencrypt/cloudflare.ini
```
Request a certificate covering the bare domain and the wildcard (required for HTTP subdomain tunnels):
```bash
certbot certonly \
--dns-cloudflare \
--dns-cloudflare-credentials /etc/letsencrypt/cloudflare.ini \
-d "edge.rustunnel.com" \
-d "*.edge.rustunnel.com" \
--agree-tos \
--email your@email.com
```
Certbot writes the certificate to:
```
/etc/letsencrypt/live/edge.rustunnel.com/fullchain.pem
/etc/letsencrypt/live/edge.rustunnel.com/privkey.pem
```
These paths are already set in the config above. Certbot sets up automatic renewal via a systemd timer.
rustunnel reads TLS certificates from disk at startup, so it must be restarted after each renewal.
Add a Certbot deploy hook to do this automatically:
```bash
cat > /etc/letsencrypt/renewal-hooks/deploy/restart-rustunnel.sh <<'EOF'
#!/bin/sh
systemctl restart rustunnel.service
EOF
chmod +x /etc/letsencrypt/renewal-hooks/deploy/restart-rustunnel.sh
```
Allow the `rustunnel` service user to read the certificates:
```bash
# Grant read access to the live/ and archive/ directories
chmod 755 /etc/letsencrypt/{live,archive}
chmod 640 /etc/letsencrypt/live/edge.rustunnel.com/*.pem
chgrp rustunnel /etc/letsencrypt/live/edge.rustunnel.com/*.pem
chgrp rustunnel /etc/letsencrypt/archive/edge.rustunnel.com/*.pem
chmod 640 /etc/letsencrypt/archive/edge.rustunnel.com/*.pem
```
### 8 — Set up systemd service
```bash
# Copy the unit file from the repository
install -Dm644 deploy/rustunnel.service /etc/systemd/system/rustunnel.service
systemctl daemon-reload
systemctl enable --now rustunnel.service
# Check it started
systemctl status rustunnel.service
journalctl -u rustunnel.service -f
```
### 9 — Open firewall ports
```bash
ufw allow 80/tcp comment "rustunnel HTTP edge"
ufw allow 443/tcp comment "rustunnel HTTPS edge"
ufw allow 4040/tcp comment "rustunnel control plane"
ufw allow 8443/tcp comment "rustunnel dashboard"
ufw allow 9090/tcp comment "rustunnel Prometheus metrics"
# TCP tunnel port range (must match tcp_port_range in server.toml)
ufw allow 20000:20099/tcp comment "rustunnel TCP tunnels"
# UDP tunnel port range (must match udp_port_range in server.toml)
ufw allow 20100:20199/udp comment "rustunnel UDP tunnels"
```
### 10 — Verify the server is running
```bash
# Health check — use dashboard_port from server.toml (default 8443 in production)
curl http://localhost:8443/api/status
# Confirm which ports the process is actually bound to
ss -tlnp | grep rustunnel-serve
# Startup banner is visible in the logs
journalctl -u rustunnel.service --no-pager | tail -30
# Prometheus metrics
curl -s http://localhost:9090/metrics
```
> **Port reminder**: port 4040 is the control-plane WebSocket (clients connect here),
> not the dashboard. Hitting it with plain HTTP returns `HTTP/0.9` which is expected.
> The dashboard is on `dashboard_port` — check your `server.toml` if unsure.
### Updating the server
Pull the latest code, rebuild, install, and restart in one command:
```bash
cd ~/rustunnel && sudo make update-server
```
This runs `git pull` → `cargo build --release` → `install` → `systemctl restart` → `systemctl status`.
---
## Docker deployment
A full Docker guide covering both local development (self-signed cert) and
production VPS (Let's Encrypt) is available in
[**docs/docker-deployment.md**](docs/docker-deployment.md).
### Quick reference
```bash
# Build the image (includes Next.js dashboard + Rust server)
make docker-build
# Local development (self-signed cert, no auth required)
docker compose -f deploy/docker-compose.local.yml up
# Production VPS (requires deploy/server.toml to be configured first)
make docker-run
# Production + Prometheus + Grafana monitoring stack
make docker-run-monitoring
# Tail server logs
make docker-logs
# Stop everything
make docker-stop
```
### Files
| File | Purpose |
|------|---------|
| `deploy/Dockerfile` | Multi-stage build: Node.js UI → Rust server → slim runtime |
| `deploy/docker-compose.yml` | Production compose file |
| `deploy/docker-compose.local.yml` | Local development compose file |
| `deploy/server.toml` | Production server config template |
| `deploy/server.local.toml` | Local development server config |
| `deploy/prometheus.yml` | Prometheus scrape config |
---
## Client configuration
### Installation
**Option 1 — Homebrew (macOS and Linux, recommended)**
```bash
brew tap joaoh82/rustunnel
brew install rustunnel
```
Homebrew installs pre-built binaries — no Rust toolchain required.
The formula is updated automatically on every release. This installs
both `rustunnel` (the CLI client) and `rustunnel-mcp` (the MCP server
for AI agent integration).
**Option 2 — Pre-built binary**
Download the archive for your platform from the
[latest GitHub Release](https://github.com/joaoh82/rustunnel/releases/latest),
extract it, and move the `rustunnel` binary to a directory on your `$PATH`:
```bash
# Example for macOS Apple Silicon
curl -L https://github.com/joaoh82/rustunnel/releases/latest/download/rustunnel--aarch64-apple-darwin.tar.gz \
| tar xz
sudo install -Dm755 rustunnel /usr/local/bin/rustunnel
```
Available targets:
| Platform | Archive |
|----------|---------|
| macOS Apple Silicon | `rustunnel--aarch64-apple-darwin.tar.gz` |
| macOS Intel | `rustunnel--x86_64-apple-darwin.tar.gz` |
| Linux x86_64 (glibc) | `rustunnel--x86_64-unknown-linux-gnu.tar.gz` |
| Linux x86_64 (musl, static) | `rustunnel--x86_64-unknown-linux-musl.tar.gz` |
| Linux arm64 | `rustunnel--aarch64-unknown-linux-gnu.tar.gz` |
| Windows x86_64 | `rustunnel--x86_64-pc-windows-msvc.zip` |
**Option 3 — Build from source**
Requires Rust 1.76+.
```bash
git clone https://github.com/joaoh82/rustunnel.git
cd rustunnel
cargo build --release -p rustunnel-client
sudo install -Dm755 target/release/rustunnel /usr/local/bin/rustunnel
# Or via make
make deploy-client
```
### Setup wizard
The easiest way to create your config file is the interactive setup wizard:
```bash
rustunnel setup
```
It prompts for your region and auth token, then writes `~/.rustunnel/config.yml` with the correct server address and a commented `tunnels:` example section.
```
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_abc123...
Created: /Users/you/.rustunnel/config.yml
Run `rustunnel start` to connect using this config.
```
Pick a specific region (`eu`, `us`, `ap`) to connect directly, or `auto` (the default) to let the client probe all regions and pick the nearest. Choose `self-hosted` if you run your own server — the wizard will then prompt for your server address.
After running setup, use `rustunnel start` to connect with all tunnels defined in the config, or use `rustunnel http ` / `rustunnel tcp ` for one-off tunnels.
### Quick start (CLI flags)
```bash
# Expose a local HTTP service on port 3000 (auto-selects nearest region)
rustunnel http 3000 \
--token YOUR_AUTH_TOKEN
# Connect to a specific region
rustunnel http 3000 --region eu --token YOUR_AUTH_TOKEN
# Use an explicit server address (bypasses region selection)
rustunnel http 3000 \
--server edge.rustunnel.com:4040 \
--token YOUR_AUTH_TOKEN
# Expose a local service with a custom subdomain
rustunnel http 3000 \
--token YOUR_AUTH_TOKEN \
--subdomain myapp
# Expose a local TCP service (e.g. a PostgreSQL database)
rustunnel tcp 5432 \
--token YOUR_AUTH_TOKEN
# Disable automatic reconnection
rustunnel http 3000 --no-reconnect
```
### Config file
Default location: `~/.rustunnel/config.yml`
```yaml
# ~/.rustunnel/config.yml
# Tunnel server address (host:control_port)
server: edge.rustunnel.com:4040
# Auth token (from server admin_token or a token created via the dashboard)
auth_token: YOUR_AUTH_TOKEN
# Region preference: auto (probe & pick nearest), or eu / us / ap.
# Omit for self-hosted / single-server setups.
region: auto
# Named tunnels started with `rustunnel start`
tunnels:
web:
proto: http
local_port: 3000
subdomain: myapp # optional — server assigns one if omitted
db:
proto: tcp
local_port: 5432
```
Start all tunnels from the config file:
```bash
rustunnel start
# or with an explicit path
rustunnel start --config /path/to/config.yml
```
### Token management
**Hosted service:** manage tokens from the [rustunnel.com](https://rustunnel.com) dashboard under **Dashboard → API Keys**. You can create, label, and revoke tokens without any CLI commands.
**Self-hosted:** create additional tokens via the dashboard API:
```bash
rustunnel token create \
--name "ci-deploy" \
--server your-server:8443 \
--admin-token YOUR_ADMIN_TOKEN
```
Or via `curl`:
```bash
curl -s -X POST https://your-server:8443/api/tokens \
-H "Authorization: Bearer YOUR_ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{"label": "ci-deploy"}'
```
---
## Port reference
| Port | Protocol | Purpose |
|------|----------|---------|
| 80 | TCP | HTTP edge — redirects to HTTPS; also ACME HTTP-01 challenge |
| 443 | TCP | HTTPS edge — TLS-terminated tunnel ingress |
| 4040 | TCP | Control-plane WebSocket — clients connect here |
| 8443 | TCP | Dashboard UI and REST API |
| 9090 | TCP | Prometheus metrics (`/metrics`) |
| 20000–20099 | TCP | TCP tunnel range (configurable via `tcp_port_range`) |
| 20100–20199 | UDP | UDP tunnel range (configurable via `udp_port_range`) |
---
## Config file reference (server)
| Key | Type | Default | Description |
|-----|------|---------|-------------|
| `server.domain` | string | — | Base domain for tunnel URLs |
| `server.http_port` | u16 | — | HTTP edge port |
| `server.https_port` | u16 | — | HTTPS edge port |
| `server.control_port` | u16 | — | WebSocket control-plane port |
| `server.dashboard_port` | u16 | `4040` | Dashboard port |
| `tls.cert_path` | string | — | Path to TLS certificate (PEM) |
| `tls.key_path` | string | — | Path to TLS private key (PEM) |
| `tls.acme_enabled` | bool | `false` | Enable built-in ACME renewal |
| `tls.acme_email` | string | `""` | Contact email for ACME |
| `tls.acme_staging` | bool | `false` | Use Let's Encrypt staging CA |
| `tls.acme_account_dir` | string | `/var/lib/rustunnel` | ACME state directory |
| `tls.cloudflare_api_token` | string | `""` | Cloudflare DNS API token (prefer env var `CLOUDFLARE_API_TOKEN`) |
| `tls.cloudflare_zone_id` | string | `""` | Cloudflare Zone ID (prefer env var `CLOUDFLARE_ZONE_ID`) |
| `auth.admin_token` | string | — | Master auth token |
| `auth.require_auth` | bool | — | Reject unauthenticated clients |
| `database.url` | string | — | PostgreSQL connection URL (required) |
| `database.captured_path` | string | `/var/lib/rustunnel/captured.db` | Per-region SQLite file for captured HTTP request bodies |
| `server.dashboard_origin` | string | `""` | Allowed CORS origin for the dashboard UI (e.g. `http://localhost:3000`) |
| `logging.level` | string | — | `trace` / `debug` / `info` / `warn` / `error` |
| `logging.format` | string | — | `json` or `pretty` |
| `logging.audit_log_path` | string | `null` | Path for audit log (JSON-lines); omit to disable |
| `limits.max_tunnels_per_session` | usize | — | Max tunnels per connected client |
| `limits.max_connections_per_tunnel` | usize | — | Max concurrent connections per tunnel |
| `limits.rate_limit_rps` | u32 | — | Per-tunnel request rate cap (req/s) |
| `limits.ip_rate_limit_rps` | u32 | `100` | Per-source-IP rate cap (req/s); `0` = disabled |
| `limits.request_body_max_bytes` | usize | — | Max proxied request body size (bytes) |
| `limits.tcp_port_range` | [u16, u16] | — | Inclusive `[low, high]` TCP tunnel port range |
| `limits.udp_port_range` | [u16, u16] | `[0, 0]` | Inclusive `[low, high]` UDP tunnel port range; `[0, 0]` disables UDP tunnels |
| `region.id` | string | `"default"` | Region identifier recorded in tunnel history (e.g. `"eu"`, `"us"`, `"ap"`) |
| `region.name` | string | `"Default"` | Human-readable region name shown in the dashboard |
| `region.location` | string | `""` | Physical location label (e.g. `"Helsinki, FI"`) |
---
## Monitoring
A Prometheus metrics endpoint is available at `:9090/metrics`:
```
rustunnel_active_sessions # gauge: connected clients
rustunnel_active_tunnels_http # gauge: active HTTP tunnels
rustunnel_active_tunnels_tcp # gauge: active TCP tunnels
```
Start with the full monitoring stack (Prometheus + Grafana):
```bash
make docker-run-monitoring
# Grafana: http://localhost:3000 (admin / changeme)
# Prometheus: http://localhost:9090
```
> **Production note:** The default Grafana password is `changeme`. Set the `GRAFANA_PASSWORD` environment variable before starting the stack in production:
> ```bash
> export GRAFANA_PASSWORD=your-strong-password
> make docker-run-monitoring
> ```
---
## REST API
The dashboard port exposes a REST API for programmatic access to tunnels, tokens, captured requests, and tunnel history. All endpoints (except the health check) require an `Authorization: Bearer ` header.
**Quick reference**
| Method | Path | Description |
|--------|------|-------------|
| `GET` | `/api/status` | Health check (no auth) |
| `GET` | `/api/tunnels` | List active tunnels |
| `GET` | `/api/tunnels/:id` | Get a single tunnel |
| `DELETE` | `/api/tunnels/:id` | Force-close a tunnel |
| `GET` | `/api/tunnels/:id/requests` | Captured HTTP requests |
| `POST` | `/api/tunnels/:id/replay/:req_id` | Fetch stored request for replay |
| `GET` | `/api/tokens` | List API tokens |
| `POST` | `/api/tokens` | Create an API token |
| `DELETE` | `/api/tokens/:id` | Delete an API token |
| `GET` | `/api/history` | Paginated tunnel history |
Full request/response schemas, query parameters, and examples are in
[**docs/api-reference.md**](docs/api-reference.md).
A machine-readable OpenAPI 3.0 spec is served at `GET /api/openapi.json` (no auth required).
---
## AI agent integration (MCP server)
rustunnel ships a `rustunnel-mcp` binary that implements the
[Model Context Protocol](https://spec.modelcontextprotocol.io) over stdio,
letting AI agents (Claude, GPT-4o, custom agents) open and manage tunnels
without any manual intervention.
### Quick setup (Claude Desktop — hosted server)
```json
{
"mcpServers": {
"rustunnel": {
"command": "rustunnel-mcp",
"args": [
"--server", "edge.rustunnel.com:4040",
"--api", "https://edge.rustunnel.com:8443"
]
}
}
}
```
### Available tools
| Tool | Description |
|------|-------------|
| `create_tunnel` | Spawn a tunnel and return the public URL |
| `list_tunnels` | List all active tunnels |
| `close_tunnel` | Force-close a tunnel by ID |
| `list_regions` | List available server regions |
| `get_connection_info` | Return the CLI command for cloud/sandbox agents |
| `get_tunnel_history` | Retrieve past tunnel activity |
### Installation
**Homebrew (macOS and Linux)** — installs `rustunnel-mcp` alongside the CLI:
```bash
brew tap joaoh82/rustunnel
brew install rustunnel
```
**Build from source:**
```bash
make release-mcp
sudo install -m755 target/release/rustunnel-mcp /usr/local/bin/rustunnel-mcp
```
Full setup guide, configuration options, and workflow examples are in
[**docs/mcp-server.md**](docs/mcp-server.md).
### Claude Code plugin
The easiest way to use rustunnel with [Claude Code](https://claude.com/claude-code).
Install the plugin and it handles all MCP configuration automatically — just enter
your token once and start asking Claude to expose ports.
```
/plugin install rustunnel
```
The plugin prompts for your server address and API token at enable time, stores
them securely, and starts the MCP server in the background. No `.mcp.json` editing
or manual setup needed.
**Plugin directory:** [`plugins/claude-code/`](plugins/claude-code/)
**Documentation:** [docs/claude-plugin.md](docs/claude-plugin.md)
### OpenClaw skill
rustunnel ships an [OpenClaw](https://openclaw.dev) skill that gives any
OpenClaw-compatible AI agent first-class knowledge of rustunnel — config file
format, authentication, tool signatures, and common workflows — without you
having to explain it.
**Skill file:** [`skills/rustunnel/SKILL.md`](skills/rustunnel/SKILL.md)
**What it covers:**
| Topic | Details |
|-------|---------|
| Config file | Location (`~/.rustunnel/config.yml`), format, named tunnels |
| First-time setup | `rustunnel setup` wizard or manual config creation |
| MCP tools | `create_tunnel`, `list_tunnels`, `close_tunnel`, `get_connection_info`, `get_tunnel_history` |
| Workflows | Webhook testing, demo sharing, cloud sandbox (no subprocess), named tunnels |
| Security | Token handling, file permissions, HTTPS-only transport |
**To load the skill in Claude Code:**
```bash
/skills load skills/rustunnel/SKILL.md
```
Once loaded, you can ask the agent things like:
> "Expose my local port 3000 as an HTTPS tunnel using rustunnel."
> "List my active tunnels and close the one forwarding port 5432."
> "Set up my rustunnel config file with my token."
The skill instructs the agent to read credentials from `~/.rustunnel/config.yml`
automatically, so you won't be prompted for your token on every invocation.
---
## Roadmap
A detailed list of shipped features and planned future work is maintained in
[**docs/ROADMAP.md**](docs/ROADMAP.md).
---
## Contributing
Contributions are welcome! Please follow these steps:
1. **Fork** the repository and create a feature branch from `main`.
2. Run `make install-hooks` once after cloning to activate the pre-push quality gate.
3. Make your changes. Ensure `make check` (fmt + Clippy) and `make test` pass locally.
4. Open a **Pull Request** with a clear description of what changed and why.
5. A maintainer will review and merge once CI is green.
### Guidelines
- Keep PRs focused — one logical change per PR.
- Add or update tests for any new behaviour.
- Follow the existing code style; `cargo fmt` is enforced by CI.
- For larger changes or new features, open an issue first to discuss the approach.
---
## License
This project is licensed under the **GNU AGPLv3 License** — see the [LICENSE](LICENSE) file for details.
---
## Contact
**João Henrique Machado Silva**
- GitHub: [@joaoh82](https://github.com/joaoh82)
- Project: [github.com/joaoh82/rustunnel](https://github.com/joaoh82/rustunnel)
- Issues & feature requests: [GitHub Issues](https://github.com/joaoh82/rustunnel/issues)