A self-hosted localhost tunnel. Expose your local development server to the internet with your own domain.
Why Tunnelr?
- Self-hosted - Your data stays on your infrastructure
- No limits - Unlimited tunnels, no time restrictions
- Simple - One command to deploy, one command to connect
- Flexible - Subdomain or path-based routing
On your VPS:
# Install Docker (if not already installed)
curl -fsSL https://get.docker.com | sh
# Clone the repo
git clone https://github.com/Aakeeo/tunnelr.git
cd tunnelr
# Configure
cp .env.example .env
nano .env # Set your domain and email
# Start the server
docker compose up -d
# Verify deployment
curl https://yourdomain.com/statusPrerequisites: Go 1.21+ installed (download here)
# Clone the repo
git clone https://github.com/Aakeeo/tunnelr.git
cd tunnelr
# Build the CLI
go build -o tunnelr ./cmd/cli
# (Optional) Move to your PATH for easy access
sudo mv tunnelr /usr/local/bin/
# Connect to your server
TUNNELR_SERVER=wss://yourdomain.com/ws tunnelr connect 3000Quick Install (one-liner):
git clone https://github.com/Aakeeo/tunnelr.git && cd tunnelr && go build -o tunnelr ./cmd/cli && sudo mv tunnelr /usr/local/bin/You'll see:
Tunnel established!
Public URL: https://yourdomain.com/t/a1b2c3
Forwarding: https://yourdomain.com/t/a1b2c3 -> http://localhost:3000
Press Ctrl+C to close the tunnel
All configuration is done via environment variables in .env:
| Variable | Description | Default |
|---|---|---|
BASE_DOMAIN |
Your domain (e.g., tunnel.example.com) |
localhost |
ROUTING_MODE |
path or subdomain (see below) |
path |
SSL_EMAIL |
Email for Let's Encrypt certificates | - |
| Path Mode | Subdomain Mode | |
|---|---|---|
| URL format | yourdomain.com/t/abc123 |
abc123.yourdomain.com |
| DNS required | Single A record | Wildcard A record (*) |
| SSL | Automatic (HTTP-01) | Requires DNS-01 setup |
Path Mode (default)
ROUTING_MODE=path
- URLs:
https://yourdomain.com/t/abc123/webhook - Just point your domain to the server - done!
- SSL works automatically
Subdomain Mode (prettier URLs)
ROUTING_MODE=subdomain
- URLs:
https://abc123.yourdomain.com/webhook - Requires: Wildcard DNS (
*.yourdomain.com) + wildcard SSL certificate - See Wildcard SSL Setup for configuration
Point your domain to your VPS by adding A record(s) in your DNS provider (Cloudflare, Namecheap, Route53, etc.).
Add one A record:
yourdomain.com → YOUR_SERVER_IP
Caddy automatically obtains SSL certificate via HTTP-01 challenge. No additional setup needed.
Add two A records:
yourdomain.com → YOUR_SERVER_IP
*.yourdomain.com → YOUR_SERVER_IP
Note: Subdomain mode requires a wildcard SSL certificate. See Wildcard SSL Setup below.
SSL works automatically. Caddy obtains certificates from Let's Encrypt using HTTP-01 challenge (Let's Encrypt verifies domain ownership by making an HTTP request to your server).
Wildcard certificates (*.yourdomain.com) require DNS-01 challenge (Let's Encrypt verifies ownership by checking a TXT record you create in DNS). Caddy needs API access to your DNS provider to create these records automatically.
Option 1: Use Cloudflare (Recommended)
- Move your domain's nameservers to Cloudflare (free)
- Get a Cloudflare API token with DNS edit permissions
- Update
docker-compose.yml:
caddy:
image: caddy:2-alpine
# ... existing config ...
environment:
- CLOUDFLARE_API_TOKEN=your_token_here- Update
Caddyfile:
*.{$BASE_DOMAIN} {
tls {
dns cloudflare {env.CLOUDFLARE_API_TOKEN}
}
reverse_proxy server:8080 {
header_up Host {host}
}
}
- Rebuild with Cloudflare DNS plugin:
FROM caddy:2-builder AS builder
RUN xcaddy build --with github.com/caddy-dns/cloudflare
FROM caddy:2-alpine
COPY --from=builder /usr/bin/caddy /usr/bin/caddyOption 2: Use Path Mode
If you don't want to set up DNS-01 challenge, use path mode instead:
ROUTING_MODE=path
URLs will be https://yourdomain.com/t/<tunnel-id>/... - no wildcard cert needed.
After deployment, check if everything is configured correctly:
curl https://yourdomain.com/statusResponse:
{
"ready": true,
"message": "Ready! Tunnel URLs: https://yourdomain.com/t/<tunnel-id>/...",
"base_domain": "yourdomain.com",
"routing_mode": "path",
"active_tunnels": 0,
"domain_check": {"ok": true, "ips": ["167.99.x.x"]}
}If there are issues, the message field will tell you what to fix.
# Expose localhost:3000
tunnelr connect 3000
# Expose a different port
tunnelr connect 8080
# Show help
tunnelr helpBy default, the CLI connects to ws://localhost:8080/ws. To connect to your deployed server:
TUNNELR_SERVER=wss://yourdomain.com/ws tunnelr connect 3000┌─────────────────────────────────────────────────────────────────────┐
│ YOUR SERVER (VPS) │
│ │
│ ┌─────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Caddy │─────▶│Tunnel Server │◀────▶│ Registry │ │
│ │ (HTTPS) │ │ (Go) │ │ (in-memory) │ │
│ └─────────┘ └──────────────┘ └──────────────┘ │
│ ▲ ▲ │
└─────────│──────────────────│────────────────────────────────────────┘
│ │
│ HTTPS │ WebSocket
│ │
┌─────┴─────┐ ┌─────┴─────┐
│ Webhook │ │ Tunnelr │
│ Provider │ │ CLI │
│ (Stripe) │ │ │
└───────────┘ └─────┬─────┘
│
▼
┌─────────────┐
│ localhost │
│ :3000 │
└─────────────┘
Flow:
- CLI connects to server via WebSocket
- Server assigns a unique tunnel ID
- Webhook provider sends request to
https://abc123.yourdomain.com - Caddy terminates SSL, forwards to tunnel server
- Server finds the tunnel by subdomain/path, forwards request via WebSocket
- CLI receives request, forwards to localhost
- Response travels back the same path
- Go 1.21+
- Docker & Docker Compose (for server deployment)
# Terminal 1: Start the server
go run ./cmd/server
# Terminal 2: Start a test HTTP server
python3 -m http.server 3000
# Terminal 3: Start the CLI
go run ./cmd/cli connect 3000
# Terminal 4: Test the tunnel
curl -H "Host: <tunnel-id>.localhost" http://localhost:8080/# Start server in path mode
ROUTING_MODE=path go run ./cmd/server
# Test
curl http://localhost:8080/t/<tunnel-id>/# Build server
go build -o server ./cmd/server
# Build CLI
go build -o tunnelr ./cmd/cli
# Build for multiple platforms
GOOS=darwin GOARCH=amd64 go build -o tunnelr-mac ./cmd/cli
GOOS=linux GOARCH=amd64 go build -o tunnelr-linux ./cmd/cli
GOOS=windows GOARCH=amd64 go build -o tunnelr.exe ./cmd/clitunnelr/
├── cmd/
│ ├── server/ # Tunnel server
│ │ └── main.go
│ └── cli/ # CLI client
│ └── main.go
├── internal/
│ └── tunnel/ # Shared tunnel logic
│ ├── protocol.go # Message types
│ └── registry.go # Tunnel registry
├── Dockerfile # Server container
├── docker-compose.yml # Production deployment
├── Caddyfile # Reverse proxy config
├── .env.example # Configuration template
└── README.md
| Feature | Tunnelr | ngrok (free) | Cloudflare Tunnel |
|---|---|---|---|
| Self-hosted | ✅ | ❌ | ❌ |
| Unlimited tunnels | ✅ | ❌ (1) | ✅ |
| Persistent URLs | ✅ | ❌ (2hr) | ✅ |
| Custom domain | ✅ | ❌ | ✅ |
| No account required | ✅ | ❌ | ❌ |
| Data privacy | ✅ | ❌ | ❌ |
MIT License - see LICENSE for details.