Tunly is a modern, lightweight, and easy-to-self-host ngrok alternative written in Rust. It allows you to expose your local development servers to the internet securely via WebSocket-based tunneling.
- Extreme Simplicity: Just one command to expose your local port.
- WebSocket-based: High-performance HTTP tunneling (inspired by Chisel).
- Auto-HTTPS: Built-in Caddy sidecar for automatic Let's Encrypt SSL.
- Auto-Reconnect: Robust client with exponential backoff.
- Custom Subdomains: Random assignment or
--namefor reserved subdomains.
Ensure you have Docker and Docker Compose installed. Point your domain (e.g., *.tunly.sh and tunly.sh) to your server's IP.
# Clone the repo
git clone https://github.com/spidervirus/tunly.git
cd tunly
# Set your domain and token
export TUNLY_DOMAIN=yourdomain.com
export TUNLY_TOKEN=mysecrettoken
# Start the server
docker compose up -d# Compile the client
cargo build --release -p tunly-client
# Register a single tunnel (random name)
./target/release/tunly-client http 3000 --server wss://yourdomain.com/tunnel --token mysecrettoken
# Register with a custom name
./target/release/tunly-client http 3000 --server wss://yourdomain.com/tunnel --token mysecrettoken --name myapp
# Register multiple tunnels in one process
./target/release/tunly-client http 3000:api 4000:admin --server wss://yourdomain.com/tunnel --token mysecrettokenNow access your site at: https://myapp.yourdomain.com
To test Tunly locally without a real domain:
- Edit /etc/hosts:
Add the following lines to map your local fake domain and a subdomain:
127.0.0.1 tunly.local 127.0.0.1 myapp.tunly.local
- Start Server:
export TUNLY_DOMAIN=tunly.local docker compose up -d - Run Client:
Use the
--insecureflag to accept the self-signed certificate from Caddy:./target/release/tunly-client 3000 \ --server wss://tunly.local/tunnel \ --token secret \ --name myapp \ --insecure
- Visit:
https://myapp.tunly.local(accept the browser security warning).
| Feature | Tunly | ngrok | frp | chisel | rathole | bore | zgrok |
|---|---|---|---|---|---|---|---|
| Language | Rust 🦀 | Go | Go | Go | Rust 🦀 | Rust 🦀 | Rust 🦀 |
| Simplicity | 🟢 Extreme | 🟢 High | 🔴 Low | 🟡 Medium | 🔴 Low | 🟢 High | 🟡 Medium |
| Self-Host | 🟢 1-Click | 🔴 SaaS | 🟡 Complex | 🟢 Simple | 🟡 Complex | 🟢 Simple | 🟢 Simple |
| Auto-HTTPS | 🟢 Yes | 🟢 Yes | 🔴 No | 🔴 No | 🔴 No | 🔴 No | 🟢 Yes |
| Multi-Tunnel | 🟢 Yes | 🟡 Paid | 🟢 Yes | 🟢 Yes | 🟢 Yes | 🔴 No | 🟢 Yes |
| Streaming | 🟢 Chunked | 🟢 Yes | 🟡 Mixed | 🟡 Mixed | 🟡 Mixed | 🔴 No | 🟢 Yes |
| Auth | 🟢 Token | 🟢 Complex | 🟡 Basic | 🟢 SSH | 🟡 Basic | 🔴 None | 🟢 Complex |
Exposing your local machine to the internet shouldn't be hard. Tunly is designed to be the easiest self-hosted option.
- A VPS (DigitalOcean, Hetzner, AWS, etc.)
- A domain name (e.g.,
tunly.sh) - Docker & Docker Compose
Set up two A records pointing to your VPS IP:
yourdomain.com->IP*.yourdomain.com->IP(Wildcard is required for dynamic subdomains)
# Clone and enter
git clone https://github.com/yourusername/tunly.git && cd tunly
# Set your variables
export TUNLY_DOMAIN=yourdomain.com
export TUNLY_TOKEN=choose_a_strong_token
# Launch with one command
docker compose up -dTunly uses Caddy as a sidecar. It will automatically:
- Provisions TLS certificates from Let's Encrypt.
- Terminate SSL for your main domain.
- Dynamically route traffic to active tunnels based on headers.
Download the binary for your platform (see Releases) or build from source:
# Build (requires Rust)
cargo build --release -p tunly-client
# Simple tunnel
./tunly-client http 8000 --server wss://yourdomain.com/tunnel --token your_token
# Multiple tunnels in one go
./tunly-client http 3000:web 4000:api 5000:worker --token your_tokenMIT