A production-grade, self-hosted tunneling solution in Rust that securely exposes local ports to the internet.
- Secure Communication: TLS-encrypted WebSocket connection between client and server
- Token Authentication: Token-based authentication with configurable expiration
- Custom Subdomains: Request specific subdomains for your tunnels
- Multi-Port Support: Route different paths to different local ports
- WebSocket Pass-through: Proxy WebSocket connections through tunnels
- Raw TCP Tunneling: Support for raw TCP data forwarding
- PostgreSQL Persistence: Optional database storage for horizontal scaling
- Rate Limiting: Protect against abuse with configurable rate limits
- Prometheus Metrics: Full observability with metrics endpoint
- TLS on Proxy: Optional TLS termination on the public proxy port
- Single-Port Mode: Run control and proxy on a single port
- Auto-Reconnect: Client automatically reconnects on connection loss
- Streaming Support: Efficient handling of large request/response bodies
- Graceful Shutdown: Clean shutdown with database cleanup
┌─────────────────────┐ ┌─────────────────────┐
│ Local Machine │ │ Public VPS │
│ │ │ │
│ ┌───────────────┐ │ │ ┌───────────────┐ │
│ │ Local Service │ │ │ │ tun-server │ │
│ │ :3000 │ │ │ │ │ │
│ └───────┬───────┘ │ │ │ Control:8080 │◄─────── Tunnel Clients
│ │ │ │ │ HTTP:8000 │◄─────── Internet Traffic
│ ┌───────▼───────┐ │ │ │ Metrics:9090 │◄─────── Prometheus
│ │ tun-client │──────WSS────►│ PostgreSQL ───│──────── Database
│ └───────────────┘ │ │ └───────────────┘ │
└─────────────────────┘ └─────────────────────┘
cargo build --release# Start the server - it will generate an auth secret
./target/release/tun-server --domain tunnel.yourdomain.com
# Or with a specific secret
./target/release/tun-server --domain tunnel.yourdomain.com --auth-secret your-hex-encoded-secretThe server will output the authentication secret on first run. Save this for generating client tokens.
# Use the tun-token utility
./target/release/tun-token --secret your-hex-encoded-secret./target/release/tun-client \
--server tunnel.yourdomain.com:8080 \
--local-port 3000 \
--token your-auth-tokenYour local service on port 3000 is now accessible at https://abc123.tunnel.yourdomain.com!
| Option | Environment Variable | Default | Description |
|---|---|---|---|
--domain |
TUN_DOMAIN |
localhost |
Domain for tunnel subdomains |
--control-port |
TUN_CONTROL_PORT |
8080 |
Port for client connections |
--http-port |
TUN_HTTP_PORT |
8000 |
Port for public HTTP traffic |
--cert-path |
TUN_CERT_PATH |
- | Path to TLS certificate |
--key-path |
TUN_KEY_PATH |
- | Path to TLS private key |
--auth-secret |
TUN_AUTH_SECRET |
(generated) | Hex-encoded auth secret |
--max-tunnels |
TUN_MAX_TUNNELS |
100 |
Max concurrent tunnels |
--request-timeout |
TUN_REQUEST_TIMEOUT |
30 |
Request timeout (seconds) |
--token-ttl |
TUN_TOKEN_TTL |
604800 |
Token TTL in seconds (default: 7 days) |
--body-size-limit |
TUN_BODY_SIZE_LIMIT |
104857600 |
Max body size (default: 100MB) |
--auth-timeout |
TUN_AUTH_TIMEOUT |
10 |
Auth timeout (seconds) |
--rate-limit-rps |
TUN_RATE_LIMIT_RPS |
100 |
Rate limit (req/sec/IP) |
--rate-limit-burst |
TUN_RATE_LIMIT_BURST |
200 |
Rate limit burst size |
--database-url |
TUN_DATABASE_URL |
- | PostgreSQL connection URL |
--server-id |
TUN_SERVER_ID |
(auto) | Server ID for horizontal scaling |
--proxy-tls |
TUN_PROXY_TLS |
false |
Enable TLS on proxy port |
--metrics-port |
TUN_METRICS_PORT |
- | Port for Prometheus metrics |
--single-port |
TUN_SINGLE_PORT |
false |
Run on single port |
--debug |
TUN_DEBUG |
false |
Enable debug logging |
| Option | Environment Variable | Default | Description |
|---|---|---|---|
--server |
TUN_SERVER |
localhost:8080 |
Server address |
--local-port |
TUN_LOCAL_PORT |
- | Local port to expose (legacy) |
--ports |
- | - | Port mappings (e.g., 3000, 3001:/api) |
--local-host |
TUN_LOCAL_HOST |
127.0.0.1 |
Local host to forward to |
--token |
TUN_TOKEN |
(required) | Authentication token |
--subdomain |
TUN_SUBDOMAIN |
- | Request custom subdomain |
--tls |
TUN_TLS |
false |
Use TLS for server connection |
--insecure |
TUN_INSECURE |
false |
Skip TLS verification |
--reconnect-delay |
TUN_RECONNECT_DELAY |
5 |
Reconnect delay (seconds) |
--max-reconnects |
TUN_MAX_RECONNECTS |
0 |
Max reconnect attempts (0=infinite) |
--debug |
TUN_DEBUG |
false |
Enable debug logging |
Route different paths to different local services:
./target/release/tun-client \
--server tunnel.yourdomain.com:8080 \
--token your-token \
--ports 3000 \
--ports 3001:/api \
--ports 3002:/wsThis routes:
/api/*→ localhost:3001/ws/*→ localhost:3002- Everything else → localhost:3000
Request a specific subdomain:
./target/release/tun-client \
--server tunnel.yourdomain.com:8080 \
--token your-token \
--local-port 3000 \
--subdomain my-appYour tunnel will be available at my-app.tunnel.yourdomain.com.
Enable horizontal scaling and persistence:
# Start with PostgreSQL
./target/release/tun-server \
--domain tunnel.yourdomain.com \
--database-url "postgres://user:pass@localhost/tun"Run migrations:
psql -d tun -f tun-server/migrations/001_initial.sqlEnable metrics endpoint:
./target/release/tun-server \
--domain tunnel.yourdomain.com \
--metrics-port 9090Available metrics:
tun_tunnels_connected_total- Total tunnels connectedtun_active_tunnels- Currently active tunnelstun_requests_total- Total requests processedtun_request_duration_ms- Request latency histogramtun_bytes_in_total/tun_bytes_out_total- Traffic counterstun_auth_success_total/tun_auth_failure_total- Auth attempts
Run control plane and proxy on the same port:
./target/release/tun-server \
--domain tunnel.yourdomain.com \
--single-port \
--http-port 443In this mode:
/ws→ Control plane (WebSocket)- Everything else → Proxy traffic
For production use, configure your DNS:
-
Add an A record for your tunnel domain pointing to your VPS IP:
tunnel.yourdomain.com -> YOUR_VPS_IP -
Add a wildcard A record for subdomains:
*.tunnel.yourdomain.com -> YOUR_VPS_IP
For HTTPS support, obtain certificates (e.g., from Let's Encrypt):
# Using certbot
sudo certbot certonly --standalone -d tunnel.yourdomain.com -d *.tunnel.yourdomain.com
# Start server with TLS
./target/release/tun-server \
--domain tunnel.yourdomain.com \
--cert-path /etc/letsencrypt/live/tunnel.yourdomain.com/fullchain.pem \
--key-path /etc/letsencrypt/live/tunnel.yourdomain.com/privkey.pemTo also enable TLS on the proxy port:
./target/release/tun-server \
--domain tunnel.yourdomain.com \
--cert-path /path/to/fullchain.pem \
--key-path /path/to/privkey.pem \
--proxy-tlsFor local testing, add entries to /etc/hosts:
127.0.0.1 tunnel.localhost
127.0.0.1 abc123.tunnel.localhost
Or use the subdomain.localhost pattern which works automatically in most browsers.
cargo testTerminal 1 (Server):
cargo run --bin tun-server -- --domain localhost --debugTerminal 2 (Client):
cargo run --bin tun-client -- --server localhost:8080 --local-port 3000 --token YOUR_TOKEN --debugTerminal 3 (Local service):
# Start any HTTP service on port 3000
python3 -m http.server 3000Then access http://SUBDOMAIN.localhost:8000 to reach your local service.
- All tunnel traffic is encrypted with TLS 1.3 (when TLS is enabled)
- Token-based authentication with configurable expiration
- Tokens are signed with HMAC-SHA256
- Request size limits prevent memory exhaustion (100MB default)
- Rate limiting protects against abuse
- Connection timeouts prevent resource leaks
- Reserved subdomains prevent system subdomain hijacking
With PostgreSQL enabled, you can run multiple tun-server instances behind a load balancer:
┌─────────────────┐
│ Load Balancer │
│ (HAProxy) │
└────────┬────────┘
│
┌────────────────┼────────────────┐
│ │ │
┌──────▼──────┐ ┌──────▼──────┐ ┌──────▼──────┐
│ tun-server │ │ tun-server │ │ tun-server │
│ (srv-1) │ │ (srv-2) │ │ (srv-3) │
└──────┬──────┘ └──────┬──────┘ └──────┬──────┘
│ │ │
└────────────────┼────────────────┘
│
┌────────▼────────┐
│ PostgreSQL │
└─────────────────┘
Each server uses its --server-id to track its own tunnels in the database.
MIT