█ █ █▀▀█ █▀▀█ █▀▄▀█ █ █ █▀▀█ █ █▀▀ █▄█▄█ █ █ █▄▄▀ █ █ █ █▀▀█ █ █ █ █▀▀ ▀ ▀ ▀ ▀▀▀▀ ▀ ▀▀ ▀ ▀ ▀ ▀ ▀▀▀▀ ▀▀▀ ▀▀▀
Expose your localhost to the internet. Instantly.
Wormhole is an open-source ngrok alternative that gives your local server a public HTTPS URL with a single command. No signup required. No config files. Just works.
wormhole http 3000 █ █ █▀▀█ █▀▀█ █▀▄▀█ █ █ █▀▀█ █ █▀▀
█▄█▄█ █ █ █▄▄▀ █ █ █ █▀▀█ █ █ █ █▀▀
▀ ▀ ▀ ▀▀▀▀ ▀ ▀▀ ▀ ▀ ▀ ▀ ▀▀▀▀ ▀▀▀ ▀▀▀
v0.1.0
╭──────────────────────────────────────────────────╮
│ Status ● connected │
│ Forwarding https://k7x9m2.wormhole.bar → ... │
│ Inspector http://localhost:4040 │
╰──────────────────────────────────────────────────╯
Requests
--------------------------------------------------------------
GET / 200 12ms
POST /webhooks/stripe 200 8ms
GET /api/users 200 34ms
- One command —
wormhole http 3000and you're live - HTTPS by default — TLS handled automatically by Cloudflare
- Custom subdomains —
wormhole http 3000 --subdomain myapp(free with GitHub login) - Traffic inspector — Built-in dashboard at
localhost:4040with live request stream - Request replay — Re-send any captured request with one click
- HAR export — Export captured traffic in standard HAR format
- Color-coded terminal — Live request log with method + status code colors
- Auto-reconnect — Exponential backoff, seamless recovery
- WebSocket passthrough — Full WebSocket support through the tunnel
- Zero config — No signup, no config file, no server to deploy
- Open source — Fully open source, MIT licensed
curl -fsSL https://wormhole.bar/install.sh | shbrew install MuhammadHananAsghar/tap/wormholego install github.com/MuhammadHananAsghar/wormhole/cmd/wormhole@latestgit clone https://github.com/MuhammadHananAsghar/wormhole.git
cd wormhole
make build
# Binary: ./wormhole# Start your local server on any port
wormhole http 3000
# => https://k7x9m2.wormhole.bar -> http://localhost:3000# One-time login via GitHub
wormhole login
# Use your own subdomain
wormhole http 3000 --subdomain myapp
# => https://myapp.wormhole.bar -> http://localhost:3000Every tunnel automatically starts a traffic inspector at http://localhost:4040:
- Live request/response stream via WebSocket
- Request detail view with headers and body
- One-click request replay
- Filter by method, status code, path
- Export as HAR file
# Custom inspector port
wormhole http 3000 --inspect localhost:5050
# Disable inspector
wormhole http 3000 --no-inspectwormhole http <port> # Expose local HTTP server
wormhole http <port> --subdomain NAME # Custom subdomain
wormhole http <port> --headless # No TUI, plain log output
wormhole http <port> --inspect ADDR # Custom inspector address
wormhole http <port> --no-inspect # Disable inspector
wormhole login # Authenticate via GitHub
wormhole logout # Remove stored credentials
wormhole status # Show auth status
wormhole uninstall # Remove wormhole from system
wormhole uninstall --purge # Also remove config (~/.wormhole/)
wormhole version # Print versionYOUR LAPTOP CLOUDFLARE EDGE (300+ cities)
┌──────────────┐ ┌─────────────────────────────┐
│ │ WebSocket │ │
│ wormhole │◄───────────────►│ Worker (request router) │
│ client │ (encrypted) │ ↕ │
│ │ │ Durable Object (tunnel) │
│ localhost │ │ • Holds your WebSocket │
│ :3000 │ │ • Proxies HTTP to you │
└──────────────┘ │ • Hibernates when idle │
└──────────────┬──────────────┘
│
*.wormhole.bar (Cloudflare DNS)
│
Public Internet
- Client opens WebSocket to nearest Cloudflare edge
- Durable Object assigns a subdomain
- HTTP requests to
*.wormhole.barhit the Worker - Worker routes to the correct Durable Object
- DO serializes the request over WebSocket to your client
- Client forwards to
localhost:3000 - Response flows back the same path
Latency: client <-> nearest CF edge (~5-20ms) + localhost (~0ms) = fast.
| Component | Technology |
|---|---|
| CLI Client | Go, Cobra, Bubbletea, Lipgloss |
| Transport | WebSocket (gorilla/websocket) |
| Edge Relay | Cloudflare Workers + Durable Objects |
| Database | Cloudflare D1 (SQLite) |
| DNS | Cloudflare DNS (wildcard *.wormhole.bar) |
| TLS | Cloudflare automatic SSL |
| Auth | GitHub OAuth |
wormhole/
├── cmd/wormhole/ # CLI entry point
├── internal/
│ ├── client/ # Tunnel client (connect, forward, display)
│ ├── transport/ # WebSocket transport layer
│ └── inspect/ # Traffic inspector (recorder, server, replay, HAR)
├── edge/ # Cloudflare Worker + Durable Object relay
│ ├── src/
│ │ ├── index.ts # Worker router + auth
│ │ └── tunnel.ts # Durable Object tunnel proxy
│ └── migrations/ # D1 schema migrations
├── pkg/config/ # User config (~/.wormhole/config.json)
├── deployments/ # install.sh, Docker, etc.
├── Makefile
└── .goreleaser.yml
Wormhole includes hardening measures to protect users who inadvertently expose sensitive local files or infrastructure details through the tunnel.
By default, wormhole blocks requests to dot segments and node_modules before they reach your local server. Matching is done after URL unescaping and path cleaning, so encoded or traversal variants are blocked too.
/.env,/.git,/.aws,/.ssh,/.docker, and any other dot segment in the path: return 403 Forbidden/node_modules/anywhere in the path: return 403 Forbidden
This prevents credentials and source control history from being served to the internet even if your local server would normally serve them.
To disable (for users who genuinely need to serve these paths):
WORMHOLE_NO_PATH_FILTER=1 wormhole http 3000The traffic inspector (localhost:4040) no longer sets Access-Control-Allow-Origin: *. CORS headers are only returned when the request's Origin is a loopback origin on the inspector's bound port. This prevents malicious websites visited in the same browser session from reading tunnel traffic via cross-origin requests.
The WebSocket upgrader's CheckOrigin is also locked down to the same policy.
When the local server is unreachable, wormhole returns a generic message (Tunnel connected, but the local service is not responding.) to the remote caller instead of the raw Go error string. Internal network topology details (host names, port numbers, error codes) are logged locally only and never sent through the tunnel.
# Run all Go tests
go test ./... -race
# Run edge tests
cd edge && npm test
# Build binary
make build
# Cross-compile all platforms
make distThis project follows test-driven development. Write failing tests first, then implement.
# Run tests in watch mode
go test ./internal/inspect/ -v -count=1
# Coverage
go test ./... -cover| Feature | ngrok (free) | Cloudflare Tunnel | Wormhole |
|---|---|---|---|
| One-command setup | Needs signup | Needs CF account | Just works |
| Custom subdomains | Paid ($8/mo) | Yes (complex) | Free |
| HTTPS | Yes | Yes | Yes |
| Traffic inspector | Basic | No | Full (replay, HAR) |
| WebSocket support | Yes | Yes | Yes |
| Open source | No | Client only | Fully open source |
| Cost | $0-$120/yr | $0 (complex) | $0 |
Wormhole is built on a dual-track architecture: Cloudflare Workers (free, no setup) + self-hosted Go relay (full control, unlimited scale).
- Phase 1 — Core tunnel (
wormhole http 3000→ public URL, WebSocket passthrough) - Phase 2 — HTTPS, custom subdomains (auto-reserve, 3/user limit), GitHub OAuth
- Phase 3 — Traffic inspector, request replay, HAR export, curl generation
- Phase 4 — Self-hosted Go relay (
wormhole server, QUIC transport, SQLite persistence) - Phase 5 — Auth & multi-user (API keys, team tokens, CF + self-hosted middleware)
- Phase 6 — Stream multiplexing (virtual streams over single WebSocket, backpressure)
- Phase 7 — Plugin system (request/response pipeline, custom auth, transforms)
- Phase 8 — Observability (Prometheus metrics, structured logs, health endpoints)
- Phase 9 — Protocol evolution (version negotiation, TLS pinning, binary framing)
- Phase 10 — Enterprise hardening (connection limits, mTLS, audit logs, RBAC)
- Phase 11 — P2P mode (
wormhole share, WebRTC direct connections, no relay) - Phase 12 — Polish & ship (homepage, docs site, video demos, package registries)
Muhammad Hanan Asghar
- GitHub: @MuhammadHananAsghar
- LinkedIn: muhammadhananasghar
MIT License. See LICENSE for details.
Built with Go + Cloudflare Workers. Runs on the edge.