Self-hosted HTTP tunnel over WebSocket. Expose localhost to the internet in 30 seconds.
Perfect for webhook development and sharing local apps with others.
# 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>/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
- 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
uv run server.pyThe 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.pyuv 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-secretThe 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 the server to Plash instantly:
./deploy-plash.shFirst-time setup:
# Install Plash CLI as uv tool
uv tool install plash-cli
# Authenticate with Plash
uvx plash_loginThe script generates requirements from pyproject.toml, configures authentication (optional), and deploys. Your app automatically runs on port 5001.
Configuration:
- Create
plash.envmanually or use script prompts - Set
TUNNEL_SECRETfor authenticated mode, or leave empty for open access - See
plash.env.examplefor 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 appFor technical details about the protocol, authentication flow, and implementation, see ARCHITECTURE.md.