How Cloudflare Tunnel Reaches a Laptop Behind NAT — the Full Path From Click to Origin
This morning I gave a laptop with no public IP a public HTTPS address
in five seconds flat. One cloudflared binary in
~/.local/bin, one --hello-world flag, and a
random trycloudflare.com URL started serving the world from
behind my home NAT — no port forward, no firewall rule, no DNS change. I
stared at the terminal for a moment, because that should be
impossible.
Nothing about that URL should have been able to reach my machine — and tracing exactly how it does takes us through QUIC streams, edge data centers, BGP announcements, and Tier-1 backbones.
The 30-second mental model
The classic way to publish a service is inbound: the internet dials your address, so your address must be public, your port must be open, and your firewall must allow a stranger to start a conversation with your machine. Cloudflare Tunnel inverts every one of those requirements — your machine dials out, and the public internet talks to Cloudflare instead.
| Step | Classic hosting | Cloudflare Tunnel |
|---|---|---|
| Who opens the connection | Visitor dials your server | Your server dials Cloudflare |
| Your IP address | Public, in DNS, scanned daily | Private or NATed, never published |
| Firewall | Inbound port open to the world | Outbound only, inbound can stay shut |
| What the visitor connects to | Your machine | Nearest Cloudflare PoP (anycast) |
| TLS certificate | You obtain and renew it | Edge terminates with Universal SSL |
Everything below is just this table with the lid taken off. There are five acts: the connector phones home, a hostname grows a tunnel address, BGP steers the visitor to the nearest PoP, the PoP finds your tunnel, and a QUIC stream carries the request the last mile to localhost.
Act 1: your server phones home
Nothing works until cloudflared — the
connector — has established its outbound links. On
startup it reads its identity (a credentials file holding the tunnel
UUID plus a secret, or a single token from the dashboard), then resolves
region1.v2.argotunnel.com and
region2.v2.argotunnel.com to discover the nearest edge data
centers. My own test run landed in SIN, which is exactly what you want
from Singapore: the shortest possible first hop.
Then come the connectivity pre-checks you can watch
in the logs: DNS resolution against both regions, a UDP probe for QUIC,
and a TCP probe for HTTP/2. The connector prefers QUIC on UDP
port 7844 and falls back to HTTP/2 on TCP port
7844 when something on the path blocks UDP. Either way the
handshake is TLS 1.3 with a post-quantum key exchange — my logs showed
curve preference X25519MLKEM768, a hybrid of the classic
X25519 elliptic curve and the ML-KEM lattice scheme, so a future quantum
computer cannot retroactively decrypt a recorded tunnel session.
Registration itself is simple to describe: the connector presents its tunnel UUID and secret to each data center and receives a connector ID in return. A running named tunnel holds four long-lived connections spread across two data centers, so any single connection, server, or entire colo can vanish without dropping the tunnel. Heartbeats keep NAT and firewall state alive on the return path, and a dead link is re-dialed with backoff while its siblings keep serving.
| Connector fact | Value |
|---|---|
| Direction | Outbound only, through your firewall and NAT |
| Endpoints | region1 / region2
.v2.argotunnel.com |
| Port | 7844, UDP (QUIC) preferred, TCP (HTTP/2) fallback |
| Links per connector | 4 connections across 2 data centers |
| Session crypto | TLS 1.3, hybrid post-quantum key exchange |
| Identity | Tunnel UUID + secret, or dashboard token |
Run a second cloudflared with the same tunnel
credentials — a replica — and you get four more links
from another machine. The edge treats every live link as an equal
candidate for traffic, which is the whole high-availability story: no
virtual IPs, no keepalived, just more outbound dialers.
Act 2: a hostname grows a tunnel address
A tunnel with no hostname is a phone with no number. For a
named tunnel you (or the dashboard) add a DNS record
pointing your hostname at the tunnel: a CNAME from
app.example.com to
<tunnel-uuid>.cfargotunnel.com. That odd-looking
target is the tunnel’s stable name inside Cloudflare’s own DNS, and
Cloudflare’s authoritative servers answer it with anycast A and
AAAA records — the same IP addresses announced from every point
of presence on the planet.
Quick tunnels skip all of this. When I ran
--hello-world, the edge minted a random name like
approval-jerusalem-peripherals-speaker.trycloudflare.com on
the spot, valid for exactly as long as my process stayed alive. No
account, no zone, no DNS edit — which is why a quick tunnel is a demo
tool, not infrastructure.
| Quick tunnel | Named tunnel | |
|---|---|---|
| Hostname | Random *.trycloudflare.com |
Your domain, your choice |
| Lifetime | Dies with the process | Persistent, survives restarts |
| DNS work | None | One CNAME to
<uuid>.cfargotunnel.com |
| Limits | ~200 concurrent requests, no SSE | None beyond your plan |
| Use for | Demos, webhooks, five-minute shares | Anything you bookmark |
Either way, the visitor’s resolver ends up with an anycast address. And that is where the story gets genuinely interesting, because that one address lives in 300-plus cities simultaneously.
Act 3: one IP in 300-plus places — anycast and BGP
Every Cloudflare point of presence announces the same IP prefixes to the internet via BGP, the protocol routers use to tell each other which addresses live behind them. Each announcement is a BGP UPDATE carrying the prefix (the NLRI, e.g. an anycast /20) with an AS_PATH that starts at AS13335, Cloudflare’s autonomous system number. Your eyeball ISP’s routers receive these announcements from every direction — from a peering session at an exchange, from a transit provider, sometimes from both — and each router independently picks one best path. Nobody coordinates; the nearest PoP simply wins nearly every independent decision.
How does a router choose? BGP best-path selection runs a strict priority list. The simplified version every operator carries in their head: highest local-pref first (routes learned from customers beat routes learned from peers, which beat routes learned from transit providers — money shapes routing), then shortest AS_PATH, then lowest origin type and MED, then the lowest interior (IGP) cost, which is the hot-potato step that hands the packet to the closest exit. So a Singapore visitor on an ISP peering at SGIX or Equinix Singapore will usually exit straight into the SIN PoP in one AS hop, while a visitor whose ISP buys transit from a Tier-1 backbone rides that backbone until it hands off to Cloudflare wherever they interconnect.
The Tier-1 networks deserve their definition: they are the dozen or so backbones that reach the entire internet without paying anyone for transit, bound together by a full mesh of settlement-free peering. They do not need Cloudflare and Cloudflare does not need them specifically — but your packets may cross one or two of them the way a connecting flight crosses hub airports. Each hop is just another longest-prefix-match lookup in another router’s FIB, built from the same global BGP table.
| BGP concept | What it does in this story |
|---|---|
| Anycast announcement | Same prefix from 300+ PoPs; every router sees many equal destinations |
| AS_PATH | Loop prevention plus distance metric; starts with AS13335 |
| Local-pref | Money-first ranking: customer routes beat peer routes beat transit |
| Hot-potato exit | ISP dumps the packet at its nearest Cloudflare handoff |
| FIB lookup | Per-packet, longest-prefix-match forwarding at line rate |
Two honest caveats. First, anycast and TCP are slightly uneasy roommates: if BGP reconverges mid-connection and your packets suddenly land at a different PoP, that PoP holds no connection state and the connection resets. In practice the global table is stable on the timescale of a page load, and QUIC’s connection migration absorbs most of the rest. Second, the return path need not mirror the outbound one — the edge replies from the anycast address and normal routing delivers it; only the ingress PoP needs the state.
Act 4: inside the PoP — finding your tunnel
The packet arrives at SIN and climbs the edge stack. First the
TLS handshake: the ClientHello carries a Server Name
Indication (app.example.com), the edge selects the matching
certificate — a Universal SSL cert it issued and renews for you, the
same automation story as in Why SSL Certificates Keep
Shrinking — and terminates the visitor’s encryption right there.
From this point the request is plaintext HTTP inside Cloudflare’s
network, which is exactly what lets the WAF, Bot Management, cache, and
Access policies inspect it.
Then comes the lookup this whole architecture exists to serve: the tunnel registry maps the hostname to a tunnel UUID, and the UUID to the set of live connector links currently holding that tunnel open. The edge picks a healthy one. If your connector dialed SIN directly, the handoff is local; if it dialed two other colos, the request rides Cloudflare’s private backbone to whichever colo holds a live link. Either way the visitor never learns which outcome happened — or that an origin server exists at all.
| Edge stage | Job |
|---|---|
| Anycast receive | Accept the packet; you are the winning PoP |
| TLS termination | SNI selects cert, handshake completes, HTTP is readable |
| Policy stack | WAF, bots, cache, Access rules run on the plaintext |
| Registry lookup | Hostname becomes tunnel UUID becomes live links |
| Handoff | Request is queued onto one healthy connector link |
Note what is missing from this table: any connection to your IP address. Authenticated Origin Pulls does nothing here — there is no inbound listener on your origin to authenticate against. Your tunnel credentials already did that job back in Act 1.
Act 5: the ride down the tunnel
The last mile runs over the links Act 1 opened. Each connection is multiplexed: many requests share one QUIC connection as independent HTTP/2-style streams, each stream carrying one request-response exchange with its own flow control. Ten visitors hitting your origin at once do not need ten connections — they get ten streams on links you already hold open, and a slow response on one stream never blocks the others the way head-of-line blocking stalls plain TCP.
Why does this cross NAT and firewalls that would reject inbound traffic? Because every packet on the wire is either outbound (your connector sent it) or reply-shaped (it arrives on a UDP or TCP flow your firewall already saw leave). Stateful middleboxes keep a table of flows they have witnessed; the tunnel’s packets always match an entry, and QUIC keepalives stop the entry from expiring. No hole punching, no UPnP, no firewall admin — just the oldest trick in middlebox traversal, dressed in modern crypto.
At your end, cloudflared demultiplexes the stream, reads
its ingress rules, and dials the target itself — typically plaintext
HTTP to 127.0.0.1:8080, though HTTPS and unix sockets work
too. The origin app answers a perfectly ordinary local request. Bytes
flow back up the same stream, the edge re-encrypts them under the
visitor’s TLS session, and the page renders.
| Layer on the wire | Protection | Who can read it |
|---|---|---|
| Visitor to edge | Visitor TLS session | Visitor, Cloudflare edge |
| Inside the PoP | Datacenter fabric | Cloudflare policy stack |
| Edge to connector | Tunnel TLS 1.3 + PQ key exchange | Cloudflare edge, your connector |
| Connector to origin | Localhost (or origin TLS if configured) | Your machine |
One accounting detail matters forever after: your origin sees
the connector’s address, not the visitor’s. For HTTP the edge
injects CF-Connecting-IP (plus
X-Forwarded-For) so your app can recover the real client;
for raw TCP, SSH, or RDP there is no header to inject and the original
source address is simply gone. Log accordingly.
The full round trip in seven messages
Put the acts together and a single click becomes seven hops: the browser resolves your hostname to an anycast address and opens TLS; BGP best-path delivers the packets to the nearest PoP; the edge terminates TLS, applies policy, and looks up your tunnel; the request rides a QUIC stream to your connector; the connector dials localhost; your app answers; and the response retraces the stream, gets wrapped in the visitor’s TLS session, and routes home. Total new firewall rules on your side: zero.
When pieces die
Redundancy in this design is measured in links, not boxes. Here is the failure table I wish someone had handed me on day one:
| Failure | What happens |
|---|---|
| One of the 4 links drops | Traffic uses the other three; the dead link re-dials with backoff |
| A whole edge colo goes dark | Your 2-colo spread keeps the tunnel alive via the survivor |
| Your connector process dies | Run a replica: the tunnel UUID accepts links from any machine with the secret |
| Origin app dies | Edge returns a 502-family error; the tunnel itself stays up |
| UDP 7844 blocked on your network | Connector falls back to HTTP/2 over TCP 7844 |
| TCP 7844 blocked too | No tunnel. This port must be reachable outbound |
The pattern to internalize: origin failures look like origin failures, but tunnel failures look like nothing at all — the links quietly re-dial while siblings carry the load.
What the tunnel hides — and what it doesn’t
| Claim | Verdict |
|---|---|
| Hides your origin IP from scanners | Yes — it appears in no DNS record and takes no inbound connections |
| Removes inbound firewall rules | Yes — outbound 7844 is the only requirement |
| Encrypts traffic in transit | Yes — twice, under two independent TLS sessions |
| Hides request plaintext from Cloudflare | No — the edge terminates visitor TLS to run WAF, cache, and Access |
| Preserves visitor IP to origin | Only for HTTP, via CF-Connecting-IP |
| Replaces authentication | No — pair it with Access policies or your own auth |
The trust boundary is the point most explanations skip, so here it is plainly: between the edge’s TLS termination and the tunnel’s re-encryption, your traffic is plaintext inside Cloudflare’s network. That is not a flaw — it is the feature that buys you WAF, caching, and bot filtering — but it means the tunnel moves your trust perimeter rather than removing it. If a workload cannot tolerate that, it does not belong behind any CDN-terminated tunnel.
The one-line takeaway
Your server dials out over QUIC, BGP anycast delivers visitors to the nearest PoP, and multiplexed streams bridge the gap — which is how a laptop behind NAT gets a global HTTPS address with zero firewall rules.
Sources: Cloudflare Tunnel docs (tunnel setup, useful terms,
connectivity options — port 7844, QUIC/HTTP/2, 4 links over 2 DCs);
Cloudflare Radar AS13335 routing pages; my own
cloudflared tunnel --hello-world session logs (SIN landing,
X25519MLKEM768, QUIC registration).