DocsGuidesExposing a local app

Exposing a local app

The whole point of ngstone: turn a port on your laptop into a public HTTPS URL.

Basic usage#

Point the agent at whatever port your app is listening on:

$ ngstone port 3000
→ https://amber-fox-42.ngstone.site

That forwards https://amber-fox-42.ngstone.site to localhost:3000. Anything the public URL receives, your local app receives.

How forwarding works#

Each incoming request opens a fresh yamux stream from the edge to the agent, carrying a small JSON preamble ahead of the raw request bytes. The agent reads the preamble, then forwards the request to your local app over a pooled, keep-alive transport. Bodies stream through end to end — nothing is buffered in full on either side, so a large upload or download costs constant memory regardless of size.

Forwarded headers#

The edge stamps X-Forwarded-For, X-Forwarded-Proto, and X-Forwarded-Host on every request before it reaches the agent, and the agent preserves them unmodified on the way to your local app.

Your local app can trust these headers. Inbound spoofed values are stripped and replaced at the edge before the request ever reaches the tunnel, so X-Forwarded-For always reflects the real public client, not something an attacker sent.

The Host header#

By default, ngstone preserves the public Host header end to end — useful for OAuth callback URLs and virtual-host routers. If your local app expects a different tenant hostname, override it with --host-header:

$ ngstone port 3000 --host-header demo.localhost:3000

The local app receives Host: demo.localhost:3000. The X-Forwarded-Host header still contains the public tunnel hostname.

When the local app isn't running#

If the agent can't dial your local port, it doesn't just fail silently — it synthesizes a 502 with a body that says what went wrong, so a request against a stopped dev server reads as an obvious, actionable error rather than a mystery.

502response
Local dial failed. Body includes a message like "nothing listening on localhost:3000".