Get a public URL for localhost
Your app runs on localhost. Nobody else can reach it. A tunnel gives you a normal HTTPS link that forwards to your machine.
That is the whole pitch. No staging deploy, no router port forwarding, no asking IT to punch a hole in the firewall.
The one-liner
ngsrv http 3000
Swap 3000 for whatever port your dev server uses. ngsrv prints a URL like https://furry-otter-1842.tnl.ngsrv.com. Open it anywhere. Traffic lands on your laptop.
Install (pick one)
# macOS
brew install ngsrv/tap/ngsrv
# macOS / Linux
curl -fsSL https://get.ngsrv.com | bash
# Windows
irm https://get.ngsrv.com/windows | iex
Create a free account at ngsrv.com/register. Copy your token from the dashboard:
ngsrv token <YOUR_TOKEN>
One time per machine.
When you need this
Webhook testing. Stripe, GitHub, Shopify, and Slack all want a public HTTPS endpoint. Point them at ngsrv instead of deploying a throwaway staging app.
Client previews. Send a link while the real code still runs on your machine. Hot reload keeps working.
Mobile testing. Hit your local API from a phone on cellular data.
Pairing with a remote teammate. They see your local branch, you keep the debugger.
Stable links vs random ones
First run gives you a random subdomain. Fine for a twenty-minute test.
For anything that lasts longer, reserve a name:
ngsrv http 3000 --subdomain my-app
# -> https://my-app.tnl.ngsrv.com
Webhook configs hate changing URLs. Clients hate links that die when you restart the CLI.
Common ports
| Port | Typical stack |
|---|---|
| 3000 | Next.js, Express, Rails |
| 4200 | Angular |
| 5173 | Vite |
| 5000 | Flask |
| 8000 | Django, Laravel, FastAPI |
| 8080 | Spring Boot, Go services |
| 4242 | Webhook sample apps |
Port-specific walkthroughs:
Full list on the blog.
Lock it down
A public URL means anyone with the link can hit your dev server. For short tests that is usually fine. For longer sessions, attach a policy you already created in the dashboard.
- Create the policy under Dashboard → Security (header auth, IP allowlist, rate limit, geo, time window, or WAF).
- Copy its ID from the policy list. IDs look like
ngsrv_hdr_ABC123orngsrv_ips_DEF456. - Reference that ID in your config. You are attaching an existing policy, not defining rules inline:
# ngsrv.yml
tunnels:
- name: web
port: 3000
subdomain: my-app
security_policies:
- ngsrv_hdr_ABC123
Run ngsrv run, or pass the same ID on the CLI: ngsrv http 3000 --policy ngsrv_hdr_ABC123.
Reusing the same policy ID does not create a duplicate policy or burn extra quota. Policy count limits apply when you create policies in the dashboard, not when you reference an ID on a tunnel.
See config docs and header auth.
ngsrv vs the usual suspects
| Tool | Friction | Stable URL | Policies |
|---|---|---|---|
| ngsrv | CLI + free account | Reserved subdomain | Built in |
| ngrok | CLI + account | Paid for stable | Mostly paid |
| localtunnel | npx, no account | Random each run | None |
| cloudflared quick | CLI or npx | Random each run | Via Zero Trust |
| localhost.run | SSH one-liner | Random | None |
Fair comparisons: vs ngrok, vs localtunnel, vs Cloudflare Tunnel.
FAQ
Is there a free plan? Yes. One tunnel, 10GB/month, 100k requests. Enough for real dev work.
Does it work on Windows? Yes. PowerShell install script, same CLI commands.
Can I use my own domain? Yes — Free includes one custom domain; Pro and Pay as you go include more. Custom domains docs.
Is the traffic encrypted? TLS terminates at the ngsrv edge. Your local connection is HTTP on loopback, same as every other tunnel tool.