A tunnel carries traffic from a public edge to a service that has no public address. A subdomain is a name that other systems store. They are separate decisions: choose the tunnel by where the service runs, and choose the name by who will store it and for how long.
Three kinds of URL for a service on your machine
| Cloudflare Quick Tunnel | ngrok free dev domain | Owned hostname | |
|---|---|---|---|
| What the URL looks like | Random name under trycloudflare.com |
Assigned name under ngrok-free.app or ngrok-free.dev |
preview.example.com |
| Who picks the name | Generated at each launch | ngrok assigns one per account; it cannot be changed on Free | You |
| After a restart | A new random name | The same name | The same name |
| Account needed | None | An ngrok account | A domain, DNS, and a tunnel or hosting account |
| Documented limits, checked 26 September 2026 | 200 in-flight requests (then HTTP 429), no Server-Sent Events, no uptime guarantee, not for production | 1 GB data transfer out and 20,000 HTTP requests per month, up to 3 online endpoints, a warning page for browser traffic | Those of the tunnel or hosting plan behind it |
| Keep the URL when switching providers | No | No | Yes |
Two assumptions are worth correcting. ngrok’s free plan does not hand out a fresh random URL per session: every account gets one automatically assigned dev domain that stays the same, cannot be customized, and has no endpoint timeout. And a Quick Tunnel needs no account at all: cloudflared tunnel --url http://localhost:8080 starts one on a random trycloudflare.com name. The one documented catch: Quick Tunnels are not supported while a config.yaml file exists in the .cloudflared directory.
ngrok also shows an interstitial warning page on free-plan HTML traffic. People using a browser see it; clients can skip it by sending an ngrok-skip-browser-warning header or a non-browser User-Agent. Using your own domain with ngrok requires its paid Pay-as-you-go plan; ngrok connects it through a CNAME record at your DNS provider and can provision the certificate for you.
Where the URL gets stored decides which one you need
Every place that stores the URL has to change when the URL does:
- webhook endpoint settings at a payment, Git hosting, or chat provider
- OAuth redirect URI allowlists
- mobile or desktop builds with a built-in API base URL
- documentation, tickets, and teammates’ bookmarks
A Quick Tunnel URL fits a single sitting: a demo, or one webhook test you re-register each time. The ngrok dev domain fits repeated personal testing, because you register it once, but it remains ngrok’s name and cannot follow you to another provider. An owned hostname fits anything shared or long-lived, because only the routing behind it changes.
Keep the choice in one variable so switching is a single edit:
PUBLIC_BASE_URL=https://preview.example.com
WEBHOOK_CALLBACK_URL=${PUBLIC_BASE_URL}/webhooks
The variable keeps your own configuration consistent; the registrations stored at other providers still have to be updated by hand.
What the provider namespaces look like in DNS
Live capture, 26 September 2026. Invented labels under each provider’s domain, asked of Cloudflare’s resolver (1.1.1.1), plus one HTTP request to ngrok’s edge:
$ dig +nocmd @1.1.1.1 zz-not-a-tunnel-0926.trycloudflare.com A +noall +comments +authority | grep -E "status|SOA"
;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, id: 14864
trycloudflare.com. 60 IN SOA kevin.ns.cloudflare.com. dns.cloudflare.com. 2415920146 10000 2400 604800 60
$ dig +nocmd @1.1.1.1 zz-not-a-tunnel-0926.ngrok-free.app A +noall +answer
zz-not-a-tunnel-0926.ngrok-free.app. 60 IN A 18.177.60.68
zz-not-a-tunnel-0926.ngrok-free.app. 60 IN A 18.176.183.3
zz-not-a-tunnel-0926.ngrok-free.app. 60 IN A 13.158.194.126
zz-not-a-tunnel-0926.ngrok-free.app. 60 IN A 18.177.76.42
zz-not-a-tunnel-0926.ngrok-free.app. 60 IN A 18.177.53.48
$ dig +short @1.1.1.1 zz-not-a-tunnel-0926.ngrok-free.dev A | wc -l
5
$ curl -sI --max-time 15 https://zz-not-a-tunnel-0926.ngrok-free.app/
HTTP/2 404
content-type: text/html
ngrok-error-code: ERR_NGROK_3200
referrer-policy: no-referrer
content-length: 2354
date: Sat, 26 Sep 2026 14:54:07 GMT
The two providers publish names differently:
trycloudflare.comansweredNXDOMAINfor a label no tunnel had claimed, with a 60-second negative TTL, so there is no wildcard: a Quick Tunnel’s name exists in DNS only once the tunnel has created it. We did not run a Quick Tunnel, so we did not observe how long a name survives after the process stops.- Both ngrok free domains resolved the invented label to five addresses with a 60-second TTL, so they behave as wildcards. DNS reports every name as existing whether or not an endpoint is online, and the real answer arrives over HTTP: a
404carryingngrok-error-code: ERR_NGROK_3200, which ngrok documents as “The endpoint is offline”, meaning the agent is not running, has crashed, or the URL is wrong.
When a webhook provider reports delivery failures, check the layer that can actually answer:
| Symptom | Where it comes from | What it means |
|---|---|---|
NXDOMAIN for a trycloudflare.com URL |
DNS | No tunnel has that name: mistyped, never created, or no longer registered |
HTTP 404 with ngrok-error-code: ERR_NGROK_3200 |
ngrok’s edge | No agent is serving that endpoint right now |
HTTP 429 from a Quick Tunnel |
Cloudflare’s edge | More than 200 requests in flight |
Cloudflare error 1016 on an owned hostname |
Cloudflare’s edge | The DNS record still points at a tunnel that is not running |
The negative caching guide explains why a name that did not exist a minute ago can keep failing for the length of that negative TTL after it is created.
Owned hostname through Cloudflare Tunnel
When you add a route for a tunnel in the Cloudflare dashboard, Cloudflare creates a DNS record pointing your hostname at <UUID>.cfargotunnel.com. The record and the tunnel are independent: if the tunnel stops, the record stays and visitors see error 1016. Cloudflare also states that a cfargotunnel.com target only proxies traffic for DNS records in the same Cloudflare account, so a leaked tunnel UUID cannot be used from another account.
Live capture, 26 September 2026. Resolving a tunnel-style target directly, using an all-zero UUID:
$ dig +short @1.1.1.1 00000000-0000-0000-0000-000000000000.cfargotunnel.com AAAA
fd10:aec2:5dae::
$ dig +short @1.1.1.1 00000000-0000-0000-0000-000000000000.cfargotunnel.com A
The A query printed nothing, and the only answer is an IPv6 address in fd00::/8, the locally assigned part of the unique local range that RFC 4193 keeps off the public Internet’s routing tables. Resolved directly, the target leads nowhere; it works only when Cloudflare’s proxy handles the hostname. That is why the route belongs in a zone on Cloudflare in the same account, not behind a CNAME at another DNS provider. The Cloudflare DNS setup guide covers how proxied and DNS-only records differ.
An owned hostname makes the name dependable before the service behind it is, so operate the connector like a service:
- run
cloudflaredunder a service manager, not in an interactive terminal - put authentication or an access policy in front of anything that is not meant to be public
- keep debug consoles and administrative routes off the published path
- monitor the connector and the application separately, because the hostname stays valid when either one is down
- decide what should happen when the machine sleeps or changes networks, since visitors will see error 1016 in the meantime
When a hosted preview is the better answer
If the code can run on a hosting platform, a preview deployment behind an owned hostname takes the connector out of the path entirely. Follow the platform’s custom-domain steps and the subdomain setup guide for record types, platform binding, and HTTPS, rather than adding a tunnel the architecture does not need.
Choosing
- Quick Tunnel: one sitting, nothing to register and nothing to keep.
- ngrok free dev domain: repeated personal testing, when a provider-branded URL and the free quotas are acceptable.
- Owned hostname through a tunnel: a URL that other people or systems store, for a service that has to stay on your machine or private network.
- Owned hostname to a hosted preview: shared or long-lived environments that can run on a platform.
If nothing outside your machine needs to reach the service, you do not need a public URL at all; local development with custom domains covers names that never leave your network.