Reserved Subdomains vs Random URLs in ngrok: A Decision Guide for Stable Endpoints
Decide between ngrok's free random URLs and paid reserved subdomains based on who depends on the endpoint, then configure and validate a stable webhook tunnel.
08 Sept 2026, 15:47 UTC

The decision: does your tunnel URL need to survive a restart?
Every ngrok tunnel gets a public hostname. On the free tier that hostname is random — something like 3f9a1b2c.ngrok.io — and it changes every time the tunnel restarts. On paid plans you can reserve a fixed subdomain such as myprodapp.ngrok.io that stays the same indefinitely. The engineering question is not which is "better" but which external systems depend on the URL, and how often the tunnel restarts.
The rule of thumb: if anything outside your machine stores the URL — a webhook configuration in Stripe or GitHub, a third-party API client, a mobile app build — you need a reserved subdomain or a custom domain. If the URL is only ever copied by a human into a browser for a quick demo, random is fine and free.
Comparing the supported options
| Option | Plan required | URL stability | Best fit |
|---|---|---|---|
| Random subdomain | Free | Changes on every tunnel start | Local demos, ad-hoc testing, one-off sharing |
| Reserved subdomain | Paid (Personal/Starter and above) | Fixed until you release it | Webhooks, staging endpoints, recurring integrations |
| Custom domain (mapped to a reserved endpoint) | Paid, higher tiers | Fixed, under your own DNS | Branded or production-facing endpoints |
Exact plan names, included subdomain counts, and pricing change over time — check the current ngrok pricing page before committing, and treat the tier names here as approximate.
Trade-offs worth pricing in
Cost and lock-in. Reserved subdomains carry a recurring fee that scales with how many you hold. Once reserved, a name generally cannot be renamed or released self-service — plan for a support request if you need to change it, so choose names carefully up front.
Operational coupling. A stable URL is a commitment. External services will keep sending traffic to it, including during incidents. If you expose an internal service on a reserved subdomain, you have permanently increased its attack surface. Put authentication in front of it (ngrok supports OAuth and IP restrictions at the edge, or you can enforce auth in your app), add rate limiting, and consider firewall or proxy rules that only allow expected sources — which is practical precisely because the hostname is known in advance.
Region and latency. Reserved subdomains are tied to the region you select. Pick the region closest to your upstream service or your webhook provider, and verify latency from the caller's side, not just your own machine.
Concrete setup: a stable webhook endpoint
Assume a local service listens on port 8080 and a payment provider must POST webhooks to a fixed URL. Reserve myprodapp in the ngrok dashboard first (this requires a paid plan; on a free account the tunnel will fail to start with a plan/permission error). Then declare the tunnel in ngrok.yml — usually at ~/.ngrok2/ngrok.yml or the path shown by ngrok config check:
authtoken: <your-authtoken>
tunnels:
prod:
proto: http
addr: 8080
host_header: rewrite
subdomain: myprodapphost_header: rewrite makes your local app see localhost:8080 as the Host header, which matters for frameworks that validate hosts. Run it from any shell with your normal user account (no elevated privileges needed):
ngrok start prodExpected result: the console prints a forwarding line showing https://myprodapp.ngrok.io -> http://localhost:8080. If you instead see an error about the subdomain not being available or not permitted, the reservation is missing or the plan doesn't cover it.
Validating the result
- Start a throwaway server on 8080 that logs the Host header (for example
python3 -m http.server 8080for basic reachability, or a small script if you need header logging). - From a machine outside your network — a cloud VM or your phone on cellular — run
curl -i https://myprodapp.ngrok.io/and confirm you get your service's response, not an ngrok error page. - Stop and restart the tunnel, then repeat the curl. The URL must be identical; that is the entire point of the reservation.
- Open the ngrok dashboard or query the local agent API at
http://127.0.0.1:4040/api/tunnelsand confirm the tunnel is listed with the reserved name and the expected region. - Compare latency against a random-subdomain tunnel in the same region; a large difference suggests a region mismatch.
Limitations
This guide assumes ngrok v3-era configuration syntax; older agents used a slightly different config layout. Reserved subdomains are specific to the ngrok.io domain family — if you need your own domain, that is the separate custom-domain feature with its own DNS setup. Finally, a reserved subdomain guarantees the name, not availability: if your agent or local service is down, the stable URL returns an ngrok error page, so monitor the endpoint like any other production dependency.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.