· NGSRV Team

How to expose localhost:3000 to the internet

ngsrv creates a public preview link for your local service. This is the short, copy-pasteable version for the single most common case: you have something running on localhost:3000 and you need a public HTTPS URL pointing at it.

ngsrv helps developers expose local services, share preview links, test webhooks, and get feedback faster. Total time: about ninety seconds.

TL;DR

# 1. Install (macOS / Linux)
curl -fsSL https://get.ngsrv.com | bash

# 2. Authenticate (paste the token from your ngsrv dashboard)
ngsrv token <YOUR_TOKEN>

# 3. Expose localhost:3000
ngsrv http 3000

You'll get a public HTTPS URL like https://furry-otter-1842.tnl.ngsrv.com. Hit it from a browser, a webhook provider, your phone — it all routes to your local server.

What's running on port 3000?

Port 3000 is the default for a lot of stacks:

  • Next.jsnpm run dev
  • Express / Fastify / Koa — most starter templates listen on 3000
  • Railsbin/rails s
  • Django — sometimes (the actual default is 8000, but lots of dev scripts override)
  • Create React App — historical default

The procedure is the same regardless. ngsrv doesn't care what the upstream is — it just forwards.

Step 1 — Install

Pick your OS:

# macOS (Homebrew, recommended)
brew install ngsrv/tap/ngsrv

# macOS / Linux (curl)
curl -fsSL https://get.ngsrv.com | bash

# Windows (PowerShell)
irm https://get.ngsrv.com/windows | iex

Verify:

ngsrv --version

Step 2 — Authenticate

Sign up at ngsrv.com/register (free, no card). Copy your token from the dashboard and:

ngsrv token <YOUR_TOKEN>

This writes ~/.ngsrv/config.yaml. One time per machine.

Step 3 — Run the tunnel

With your dev server already running on port 3000:

ngsrv http 3000

Output looks like:

ngsrv (v2.3.1)
session     #a83b
forwarding  https://furry-otter-1842.tnl.ngsrv.com -> http://localhost:3000
region      eu-west-1
status      online · 0ms

press Ctrl-C to stop

That https://... URL is now public.

Step 4 (optional) — Reserve a stable subdomain

Random subdomains rotate each session. If you're sharing this with a client or wiring it into a webhook provider, reserve a name:

ngsrv http 3000 --subdomain my-app
# -> https://my-app.tnl.ngsrv.com

Reserved subdomains live across restarts on Pro and above.

Step 5 (optional) — Lock it down

Don't want strangers on the internet poking your dev server? Drop a ngsrv.yml next to your code:

tunnels:
  - name: web
    port: 3000
    subdomain: my-app
    security_policies:
      - type: header_required
        header: "X-Preview-Token"
        value: "swap-me"

Then ngsrv run ngsrv.yml. Anything without the header gets a 401 at the edge — your local server never sees the request.

When things go wrong

connection refused — Your dev server isn't actually running on port 3000. Run lsof -i :3000 (macOS / Linux) or netstat -ano | findstr :3000 (Windows) to confirm.

websocket disconnected, reconnecting… — Network blip. ngsrv reconnects automatically. If it persists, check your corporate proxy / VPN.

401 invalid token — Your CLI token is missing or expired. Re-run ngsrv token <YOUR_TOKEN> from the dashboard.

Pages render but assets fail. Most likely Next.js / Vite refusing the host. For Next.js, add the ngsrv hostname to experimental.allowedDevOrigins. For Vite, set server.allowedHosts in vite.config.ts.

What this gives you over a deploy-to-staging loop

  • No CI wait. The link is live the moment your dev server is.
  • Real debugger. Breakpoints work, hot reload works, your editor stays in charge.
  • Stable URLs. Reserved subdomains and custom domains don't change when you restart.