Skip to content

A simple WebSocket-based tunneling service that exposes local HTTP services to the internet.

License

Notifications You must be signed in to change notification settings

vacmar01/tunnels

Repository files navigation

Tunnels

Self-hosted HTTP tunnel over WebSocket. Expose localhost to the internet in 30 seconds.

Perfect for webhook development and sharing local apps with others.

Quick Start

# Server (run on your VPS/cloud)
uv run server.py

# Client (run locally)
uv run client.py --server-url <your-vps-url> --local-url http://localhost:<your-port>

# Access your local service from anywhere
curl http://<your-vps-url>/

Why Tunnels?

vs ngrok: No account required, no rate limits, self-hosted, but HTTP-only and single-client

vs bore: Works through HTTP-only firewalls where raw TCP won't pass, but less general (can't tunnel SSH/databases)

vs localtunnel: Similar goal but you own the server, proper auth via HMAC challenge-response

Strengths: Simple, secure auth, easy self-hosting (pla.sh one-liner), WebSocket transport for firewall traversal, ~400 lines of readable Python

Limitations: Single client per server, HTTP only, no TLS termination built-in

Use Cases

  • Webhook development: Test Stripe, GitHub webhooks locally without production servers
  • Quick sharing: Show your WIP app to a friend/client instantly
  • Restrictive networks: Works behind corporate proxies and firewalls that only allow HTTP/WebSocket

Usage

Start the server

uv run server.py

The server runs on http://0.0.0.0:5001 by default.

# Using CLI arguments
uv run server.py --host 0.0.0.0 --port 5001 --secret my-secret

# Using environment variables
export TUNNEL_HOST=0.0.0.0
export TUNNEL_PORT=5001
export TUNNEL_SECRET=my-secret
uv run server.py

Start the client

uv run client.py --server-url http://localhost:5001 --local-url http://localhost:3000
# Using CLI arguments (quick one-off connections)
uv run client.py --server-url http://localhost:5001 \
  --local-url http://localhost:3000 \
  --secret my-secret

# Using environment variables (persistent configuration)
export TUNNEL_SERVER_URL=http://localhost:5001
export TUNNEL_LOCAL_URL=http://localhost:3000
export TUNNEL_SECRET=my-secret
uv run client.py

# Or use the console script (after running: uv pip install -e .)
tunnel-client --server-url http://localhost:5001 --local-url http://localhost:3000
tunnel-client --server-url http://localhost:5001 --local-url http://localhost:3000 --secret my-secret

Authentication

The server supports optional authentication using a shared secret via challenge-response HMAC.

Set TUNNEL_SECRET or use --secret on both server and client.

If no secret is provided on the server, any client can connect (open access mode).

Deploy to Plash

Deploy the server to Plash instantly:

./deploy-plash.sh

First-time setup:

# Install Plash CLI as uv tool
uv tool install plash-cli

# Authenticate with Plash
uvx plash_login

The script generates requirements from pyproject.toml, configures authentication (optional), and deploys. Your app automatically runs on port 5001.

Configuration:

  • Create plash.env manually or use script prompts
  • Set TUNNEL_SECRET for authenticated mode, or leave empty for open access
  • See plash.env.example for template

Management:

uvx plash_view          # Open app in browser
uvx plash_logs          # View app logs
uvx plash_apps          # List deployed apps
uvx plash_delete        # Delete app

Architecture

For technical details about the protocol, authentication flow, and implementation, see ARCHITECTURE.md.

About

A simple WebSocket-based tunneling service that exposes local HTTP services to the internet.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published