Tunnel anatomy
How a JustTunnel request gets from a public URL to your localhost — the WebSocket, the edge, and the lifecycle.
A JustTunnel "tunnel" is a long-lived WebSocket between the CLI on your machine and the JustTunnel edge. Public HTTPS requests hit the edge, get framed onto that WebSocket, and your CLI proxies them to localhost. Knowing the wiring is what lets you debug the failure modes — disconnects, slow responses, 502s — without guessing.
How it works
Public Internet JustTunnel edge Your machine
┌──────────────────┐ HTTPS ┌────────────────────┐ WS frame ┌────────────────┐
│ visitor / curl / │ ──────────▶ │ subdomain router + │ ────────────▶ │ justtunnel CLI │
│ webhook sender │ │ reverse proxy │ │ (your laptop) │
└──────────────────┘ └─────────┬──────────┘ └───────┬────────┘
│ │
│ long-lived WebSocket │ HTTP
│ (one per active tunnel) │ to localhost
▼ ▼
┌────────────────┐
│ localhost:3000 │
└────────────────┘
Three pieces, three responsibilities:
- The CLI opens a WebSocket to the edge as soon as you run
justtunnel <port>. The WebSocket carries every inbound HTTP request as a binary frame. Seejusttunnel [port]. - The edge terminates TLS, routes by subdomain (see Subdomains), and reverse-proxies the request onto the matching WebSocket session. There is exactly one active WebSocket per tunnel.
- The local server receives a normal HTTP request from the CLI on
127.0.0.1:<port>. From your application's perspective the request looks like any other localhost call.
Connection lifecycle
A tunnel goes through four states. Watching them in the CLI banner is the fastest way to diagnose problems.
| State | What's happening | How you see it |
|---|---|---|
| Connecting | TLS handshake + WS upgrade to the edge | Banner shows a spinner before the green check |
| Online | WebSocket open, edge has a route entry | Status ● Online in the banner |
| Idle | No request in flight; WebSocket still open | Same Online banner — idle is invisible |
| Reconnecting | WebSocket dropped; CLI retries with a token | reconnecting… line, then Online again |
Reconnects use a server-issued token bound to your tunnel id, user id, and subdomain. The token expires 24h after issue (reconnectTokenMaxAge in internal/ws/handler.go:91); reconnections after that just lose the original subdomain, which only matters for reserved names.
Free-plan tunnels also have an inactivity reaper: 2 hours with no traffic and the edge closes the WebSocket. Paid plans (Starter, Pro, Team) disable the reaper entirely. See Plans and limits for the per-tier numbers.
What flows over the WebSocket
Every inbound request is framed onto the same WebSocket — there is no second connection per request. That has three useful properties:
- No NAT or firewall changes. The CLI dials out; the edge never has to reach in.
- Headers and bodies pass through unchanged. The CLI is a transparent reverse proxy. Cookies, auth headers, request bodies, and arbitrary verbs (
PATCH,PROPFIND, etc.) all work. - One slow request doesn't block others. Frames are multiplexed; concurrent requests interleave on the same WebSocket.
The CLI applies a per-request timeout when forwarding to your local server (default 30s, override with --local-timeout). That timeout is local-side only — it does not affect the WebSocket itself.
Limits and guarantees
What the edge actually enforces today, sourced from internal/plan/limits.go:
- Concurrent tunnels per account. Free
1, Starter2, Pro5, Team10per seat. Enforced at tunnel-create time (internal/plan/enforcer.go:53). - Per-tunnel rate limit. Free
60req/min, Starter300, Pro1000, Team2000. Returned as429at the edge. - Inactivity reap (free only). 2 hours with no requests closes the WebSocket. Paid plans never time out on inactivity.
- Per-request local timeout. Default
30s, configurable per invocation. Times out at the CLI, not the edge.
Best-effort, not guaranteed:
- Reconnect across edge restarts. Reconnect tokens are valid for 24h and across most edge events, but a hard server-side restart may invalidate in-flight tokens.
- Subdomain stability for free plans. Free tunnels can request a name on each invocation but cannot hold it across restarts. See Subdomains.
Related
justtunnel [port]— the command that opens a tunnel.- Subdomains — how the edge picks which tunnel a request belongs to.
- Password protection — how the edge gates traffic before it hits your machine.
- Plans and limits — per-tier caps that govern tunnel creation and runtime.
- Tighten the local-target timeout — change the per-request timeout.
- Tunnel keeps disconnecting — diagnose reconnect loops.