Core Concepts
A handful of ideas recur throughout ngstone's design. Once these click, the rest of the docs — the wire protocol, the failure modes, the operations guides — read as consequences of a small set of decisions rather than a pile of unrelated facts.
The edge (ngstoned)#
Runs on a public Linux VPS. Terminates public TLS, issues certificates, and routes each incoming request to the agent session that owns its hostname.
The agent (ngstone)#
Runs on your dev machine (macOS or Linux). Dials out to the edge and forwards traffic to a local port.
ngstoned
The edge. Public TLS, certificate issuance, request routing.
ngstone
The agent and CLI. Also opens the terminal request inspector — see below.
The agent always initiates the connection to the edge — the VPS never dials into your laptop. That single design choice is why ngstone needs no inbound firewall holes at home and no NAT traversal.
Sessions and streams#
The agent dials ngstone.site:4443 and establishes one long-lived TLS connection. On top of that connection runs a single yamux session, which multiplexes many independent byte streams over the one TCP connection.
The first stream opened on a session is a dedicated control stream — it carries authentication, bind/unbind requests, and heartbeats. Every proxied HTTP request the edge forwards to the agent gets its own stream, opened fresh and closed when the request completes.
one TLS connection└── yamux session ├── control stream (auth, bind, ping/pong, goodbye) ├── request stream (one HTTP exchange) ├── request stream (one HTTP exchange) └── ...Bindings and the registry#
A Binding maps a hostname (like api.ngstone.site) to the agent session that owns it. The edge keeps all bindings in a Registry: a plain map guarded by an RWMutex, plus a secondary index from session to the set of hostnames it owns. That secondary index is what lets a dying session's cleanup release exactly its own bindings without racing a newer session that just took the same name.
Routing a request is a read lock and a map lookup. A miss returns an immediate 404without ever touching the agent; a hit hands the request to that binding's session.
Instance identity#
Every agent process mints a stable InstanceIDonce, at startup, and keeps it for the process's lifetime — it survives reconnects but dies with the process. This identity is what authorizes taking over a binding: if a reconnecting agent presents the same InstanceID as the current holder of a subdomain, the takeover is immediate, regardless of whether the old session still looks alive.
The inspector#
ngstone port opens a full-screen terminal dashboard — not a web UI, no localhost:4040, no HTTP API — showing every request and response your tunnel handles as it happens. One agent process runs exactly one tunnel and one inspector; a second tunnel is a second terminal tab.