Expose your local web server to the internet through secure WebSocket connections.
- β‘ WebSocket-based - Simple, reliable persistent connections (WSS)
- π Automatic HTTPS - Let's Encrypt certificates managed automatically
- π― Custom subdomains - Choose your own or get a random one
- π Cross-platform - Works anywhere with WebSocket support
- π No Nginx needed - Pure Go handles everything on port 443
- π¦ Easy deployment - Docker or standalone binary
Docker (Recommended):
# Create .env file
cat > .env << EOF
DOMAIN=your-domain.com
[email protected]
ENABLE_HTTPS=true
EOF
# Deploy
docker-compose -f docker-compose.prod.yml up -d
# Check logs
docker-compose -f docker-compose.prod.yml logs -fFrom Source:
# Build
go build -o bin/tunnel-server ./cmd/server
# Run
WS_PORT=443 DOMAIN=your-domain.com ENABLE_HTTPS=true ./bin/tunnel-serverSee DOCKER-DEPLOY.md for detailed deployment guide.
Add these DNS records for your domain:
Type Name Value
A your-domain.com YOUR_SERVER_IP
A *.your-domain.com YOUR_SERVER_IP
Quick Start:
cd client
npm install
node client.js myapp 3000Your local server at localhost:3000 is now accessible at:
https://myapp.your-domain.com
cd client
# Install dependencies
npm install
# Configure (optional - copy and edit .env)
cp .env.example .env
# Run with custom subdomain
node client.js myapp 3000
# Or with random subdomain
node client.js - 3000See client/README.md for full client documentation.
Open client/client.html in your browser for an interactive demo with UI.
Connect to wss://your-domain.com/tunnel and send a registration message:
Registration:
{
"type": "register",
"timestamp": "2025-10-24T12:00:00.000Z",
"data": {
"subdomain": "myapp",
"local_addr": "localhost:3000",
"local_port": 3000
}
}Success Response:
{
"type": "success",
"timestamp": "2025-10-24T12:00:00.000Z",
"data": {
"tunnel_id": "uuid",
"subdomain": "myapp",
"full_domain": "myapp.your-domain.com",
"local_addr": "localhost:3000",
"message": "Tunnel created: https://myapp.your-domain.com -> localhost:3000"
}
}Keep-Alive: Send ping messages every 30 seconds:
{
"type": "ping",
"timestamp": "2025-10-24T12:00:00.000Z"
}Example (Go):
conn, _, _ := websocket.DefaultDialer.Dial("wss://your-domain.com/tunnel", nil)
conn.WriteJSON(map[string]interface{}{
"type": "register",
"data": map[string]interface{}{
"subdomain": "myapp",
"local_addr": "localhost:3000",
"local_port": 3000,
},
})See client/README.md for Python and other examples.
βββββββββββββββ
β Client β
β (Your Code) β
ββββββββ¬βββββββ
β WSS (port 443)
βΌ
βββββββββββββββββββββββ
β Tunnel Server β
β - Port 80: HTTP β
β - Port 443: HTTPS β
β + WSS β
ββββββββ¬βββββββββββββββ
β
βΌ
βββββββββββββββββββββββ
β Internet Users β
β https://*.your.com β
βββββββββββββββββββββββ
Flow:
- Client connects via WSS on port 443
- Server assigns subdomain (e.g.,
myapp.your-domain.com) - Internet users visit the subdomain
- Traffic routes through WebSocket to client
- Client forwards to local server
Key Features:
- Single port (443) handles both HTTPS proxy and WebSocket
- Automatic SSL/TLS with Let's Encrypt
- No SSH required
- Native Go implementation (no Nginx/Apache needed)
| Variable | Default | Description |
|---|---|---|
DOMAIN |
(required) | Your domain name |
WS_PORT |
443 | WebSocket server port |
HTTP_PORT |
80 | HTTP server port |
HTTPS_PORT |
443 | HTTPS server port |
ENABLE_HTTPS |
true | Enable HTTPS/WSS with Let's Encrypt |
LETSENCRYPT_EMAIL |
(empty) | Email for Let's Encrypt notifications |
REQUEST_TIMEOUT |
30s | Timeout for proxied requests |
CERT_CACHE_DIR |
./certs | Certificate cache directory |
Create client/.env:
TUNNEL_SERVER=wss://your-domain.com/tunnel
SUBDOMAIN=myapp
LOCAL_PORT=3000
LOCAL_HOST=localhostSee DOCKER-DEPLOY.md for complete production deployment guide.
Quick Deploy:
# On your server
cat > .env << EOF
DOMAIN=your-domain.com
[email protected]
ENABLE_HTTPS=true
EOF
docker-compose -f docker-compose.prod.yml up -d# Run server locally
WS_PORT=8080 HTTP_PORT=8081 ENABLE_HTTPS=false make run
# Run client (another terminal)
cd client
TUNNEL_SERVER=ws://localhost:8080/tunnel node client.js myapp 3000# Check what's using port 443
sudo lsof -i :443
# Stop conflicting service (e.g., nginx)
sudo systemctl stop nginx# Remove old certificates
docker-compose -f docker-compose.prod.yml down
docker volume rm tunnel_tunnel-certs
docker-compose -f docker-compose.prod.yml up -d# Verify server is accessible
curl -I https://your-domain.com/health
# Check WebSocket endpoint
wscat -c wss://your-domain.com/tunnel# Test DNS
nslookup your-domain.com
nslookup test.your-domain.com
# Verify wildcard works
dig +short *.your-domain.comThis project started as an SSH-based tunnel (like Serveo/ngrok). We've migrated to WebSocket for:
- β Simpler protocol (no SSH key management)
- β Better browser compatibility
- β Easier debugging
- β More flexible authentication options
- β Native HTTPS support
Old SSH approach is removed. If you need SSH tunneling, check git history or use established tools like ngrok.
Contributions welcome! Please:
- Fork the repository
- Create feature branch (
git checkout -b feature/amazing) - Commit changes (
git commit -m 'Add amazing feature') - Push to branch (
git push origin feature/amazing) - Open Pull Request
MIT License - see LICENSE file for details.
- π Documentation: DOCKER-DEPLOY.md, client/README.md
- π Issues: GitHub Issues
- π¬ Discussions: GitHub Discussions
Built with:
- gorilla/websocket - WebSocket implementation
- golang.org/x/crypto/acme/autocert - Let's Encrypt integration