Runbook / guydev.ca / Concepts Deep Dive
12 Questions
Appendix C — Concept Conversations

How the Tunnel Really Works

Every conceptual question asked and answered during the guydev.ca Cloudflare Tunnel deployment — covering stateful connections, CGNAT, TCP vs UDP, TLS sessions, HTTP/2 multiplexing, and why port forwarding can never work on a Telus residential line.

TCP/UDP CGNAT HTTP/2 TLS Cloudflare Tunnel Stateful connections
Q 01

Stateful pipe vs stateless HTTP — is the connection persistent?

The whole process can work because cloudflared triggers the connection to Cloudflare and from there the connection will not be broken — not like typical HTTP request/response which is stateless. Am I right?

Mostly right, and the intuition is sharp — but the precise mechanism is richer. There are two layers to separate cleanly.

What you got exactly right

cloudflared initiates the connection outward. That is the entire reason it works on Telus CGNAT — the gate has no problem with outbound traffic, only inbound. Once cloudflared has punched out through the gate and reached Cloudflare's edge, it holds that connection open permanently. Cloudflare never needs to initiate anything inward — it just uses the connection that is already sitting there waiting.

The two-layer picture

The transport layer — the raw TCP connection that cloudflared opens to Cloudflare's edge — stays open continuously. cloudflared sends keepalive signals so the connection never goes idle long enough to be closed. This is the tunnel itself. It is not stateless. It is a permanent pipe.

The application layer — individual visitor requests (someone loading guydev.ca) — travel through that pipe as separate, independent HTTP transactions. These are stateless in the HTTP sense. Each visitor request goes in one end of the pipe, gets forwarded to IIS, IIS sends the response back through the pipe, and that transaction is done. But the pipe itself stays open the whole time, ready for the next request.

Persistent pipe carrying stateless HTTP transactions
Persistent TCP pipe — cloudflared holds this open forever Visitor Req 1 Visitor Req 2 Visitor Req 3 Each request is stateless (HTTP) — the pipe carrying them is stateful (TCP)
One-sentence summary cloudflared holds a persistent outbound pipe open, and individual visitor requests ride through that pipe as ordinary stateless HTTP — the gate only ever saw outbound traffic leave, and nothing inbound ever needed to knock.
Q 02

QUIC vs HTTP/2 — why did the tunnel drop every 4–5 minutes?

These lines are examples of cloudflared logs showing all connections dropping simultaneously with "timeout: no recent network activity".

The log tells the story precisely. All four connections registered successfully using QUIC (UDP). Exactly 4 minutes 35 seconds later, all four dropped simultaneously.

Root cause

Telus CGNAT is expiring your UDP NAT state after ~4–5 minutes of idle time. QUIC runs over UDP. The CGNAT device tracks UDP flows in a state table with a short idle timeout. When no traffic crosses that flow within the timeout window, the NAT entry is silently dropped. cloudflared's keepalive packets never reach Cloudflare's edge because the NAT device has already discarded the mapping. All four connections die together because they all share the same NAT device.

The fix

Add one line to config.yml to force HTTP/2 over TCP. TCP NAT state survives much longer on residential CGNAT devices (typically 30+ minutes), and TCP keepalives are honoured correctly.

config.yml — required line
# Force HTTP/2 (TCP) instead of QUIC (UDP) # Required on Telus residential CGNAT protocol: http2

After restarting, watch the startup logs — all four connections must show protocol=http2, not protocol=quic. Wait 6+ minutes idle and confirm no ERR lines appear.

Q 03

UDP and TCP confusion — what actually changes between QUIC and HTTP/2?

I am confused about "when cloudflared was using QUIC (UDP) ... when you forced HTTP/2 (TCP)" — which used UDP and TCP interchanged?

The confusion is completely understandable because "the tunnel" and "the protocol carrying the tunnel" are two different things layered on top of each other. There are three distinct layers stacked on top of each other, and it is easy to conflate them.

The three layers

Layer 1 — Visitor HTTP request: stateless, identical in both modes. This is what the visitor's browser sends.

Layer 2 — cloudflared tunnel: the persistent pipe concept, identical in both modes. cloudflared's job does not change.

Layer 3 — Transport protocol: this is the only layer that changed. In the before state, the tunnel was wrapped in QUIC (which runs over UDP). In the after state, the tunnel is wrapped in HTTP/2 (which runs over TCP). CGNAT only ever sees this outer layer.

Why CGNAT behaves differently

TCP has a built-in handshake — SYN, SYN-ACK, ACK. When the CGNAT device sees a TCP connection open, it writes "ESTABLISHED" next to the logbook entry and sets a 30+ minute idle timer. TCP's own keepalive mechanism resets that timer. The entry never expires.

UDP has no handshake at all — it is fire-and-forget. CGNAT cannot tell whether a UDP flow is long-lived or a one-off. It applies a short, conservative timer of 4–5 minutes. When cloudflared goes quiet because no visitors are loading the site, the entry expires, the checkpoint forgets the flow, and all four connections collapse at the same moment.

CGNAT logbook — what changes between UDP and TCP
UDP / QUIC Flow created timer: ~5 min Traffic stops no signal to CGNAT Entry DELETED tunnel dies TCP / HTTP/2 ESTABLISHED timer: 30+ min Traffic stops TCP keepalive resets Entry KEPT tunnel survives
Key insight The tunnel content — your visitor's HTTP requests, cloudflared's identity, your ingress rules — is completely invisible to CGNAT. It only ever sees the outer transport wrapper. The tunnel did not change. Only the wrapper changed.
Q 04

Pack / unpack — how does cloudflared handle the request and response?

(1) Cloudflared will unpack the HTTP request when received, then deliver it to the target IIS?
(2) Cloudflared will pack the HTTP response from laptop's IIS and then send it to Cloudflare through the pipe?
(3) Once Cloudflare receives the data, then unpack it and send to the visitor's browser in HTTP response format?
(4) It must be Cloudflared that initiates the connection to Cloudflare through TCP. If it is reversed, then Telus's CGNAT will reject it, am I right?

All four answers are yes. A few precise details worth adding:

On : cloudflared does not fully "unpack" in the sense of decrypting the visitor's data. It strips the tunnel's own encapsulation layer (the HTTP/2 framing that wraps the request for transport through the pipe), then makes a fresh, independent HTTPS connection to localhost:443. To IIS, it looks like any ordinary local client — IIS has no idea a visitor in another country is on the other end.

On : in Phase 2, Cloudflare does send data toward cloudflared — but it is not initiating a new connection. It is sending data on the TCP connection that cloudflared already opened in Phase 1. What CGNAT blocks is an entirely new inbound SYN packet arriving from Cloudflare's servers that has no matching outbound record in the state table. That packet would be silently dropped.

Full request/response sequence — two phases
Visitor Cloudflare cloudflared IIS CGNAT — Phase 1: tunnel setup — TCP SYN outbound ④ SYN-ACK (return on existing — allowed) ESTABLISHED — 4 connections held open — Phase 2: live request / response — HTTPS GET request through tunnel (TCP state exists) HTTPS localhost ① HTTP response back repacked into tunnel ② HTTPS response ③ ① strip tunnel encapsulation → fresh HTTPS to IIS ② pack IIS response into tunnel → send to Cloudflare ③ Cloudflare strips tunnel → HTTPS to visitor ④ cloudflared always initiates — if Cloudflare tried inward, CGNAT has no state entry and drops it silently
Q 05

CGNAT logbook — how does return traffic get through?

Once cloudflared initiates an outbound connection to Cloudflare, all the traffic from Cloudflare to cloudflared will be treated as responses and will be allowed by Telus because there is an entry in its logbook, am I right?

Yes, exactly right. The moment cloudflared completes the TCP handshake outbound, both NAT devices write a state entry:

CGNAT state table entry (conceptual)
Source: laptop 192.168.1.x : port 54231 Router WAN: CGNAT 100.72.x.x : port 54231 ← home router rewrites Public: Cloudflare 142.x.x.x : port 61847 ← Telus CGNAT rewrites again Destination: Cloudflare 198.41.192.x : port 443 State: ESTABLISHED Timer: 30+ min (TCP) — resets on any traffic

Every packet arriving from Cloudflare's IP on port 443 matching that entry is passed inward automatically. Neither NAT device reads the content of the packets — they do not know or care that what is flowing through is a visitor's HTTP request being proxied from Cloudflare. All they see at the transport layer is: "a packet arrived from a known destination on a known port matching an established TCP flow — let it through."

One small precision: the traffic from Cloudflare is not technically a "response" in the HTTP sense — it can carry new visitor requests arriving minutes or hours after the original TCP handshake. But from the NAT devices' perspective at the TCP layer, it is always return traffic on an established connection, which is indistinguishable from a genuine response. That distinction — HTTP stateless requests riding inside a stateful TCP connection — is the core layering of this entire setup.

The two-NAT detail There are actually two layers of NAT between your laptop and the public internet — your home router translating from 192.168.x.x to 100.x.x.x, and Telus CGNAT translating from 100.x.x.x to 142.x.x.x. cloudflared's outbound TCP crosses both, both write entries, and from that point both allow the return path.
Q 06

Can port forwarding on the Telus router avoid all this work?

Can I setup port forwarding in Telus's CGNAT device to avoid so many works?

No — and the reason is fundamental to what CGNAT actually is. Port forwarding on your home router does nothing against CGNAT.

Your home router has a port forwarding feature. The problem is that your router's WAN interface is not on the public internet. It has a private address in the 100.64.x.x range — that is the RFC 6598 CGNAT address space — assigned by Telus's equipment sitting upstream. You are forwarding a port on a private address that no outside machine can reach in the first place.

Why port forwarding cannot reach through CGNAT
Public internet visitor Telus CGNAT Telus's equipment you have zero access real public IP here Home router WAN: 100.x.x.x not a public IP Laptop — IIS 192.168.x.x GuyStore ✕ actual wall — no access ✓ port forwarding covers here — but useless

Port forwarding only moves traffic between your router and your laptop — it does nothing to the wall upstream of your router. Telus's CGNAT equipment is shared infrastructure serving thousands of customers simultaneously. There is no support ticket, no settings panel, and no phone call that changes this. It is a fundamental property of residential internet on Telus.

Cloudflare Tunnel (current)Telus Business Static IP
Monthly costFree~$80–150 CAD/month more
SetupDone — already workingPort forward + firewall rules
SecurityNo inbound ports openPort 443 exposed to internet
DDoS protectionCloudflare absorbs itYour laptop absorbs it
TLS certificateAutomatic via CloudflareYou manage it yourself
Bottom line The Cloudflare Tunnel setup is not a workaround — it is the standard approach built specifically because CGNAT makes traditional port forwarding impossible for residential customers. The setup work is one-time. The system now runs automatically and is more secure than an open port would be.
Q 07

The WAN IP as the smoking gun for CGNAT

So the key is my router for residential whose WAN IP is a private one just falls on 100.x.x.x.

Exactly right. That 100.x.x.x address is the smoking gun that tells you immediately you are behind CGNAT. The 100.64.0.0/10 address block — covering 100.64.x.x through 100.127.x.x — was reserved by the IETF specifically for carrier-grade NAT. It is called the "shared address space." When you log into your Telus home router and look at the WAN IP, if it starts with 100., you are definitively behind CGNAT.

You can verify this by comparing your router's WAN IP with your true public IP:

PS — PowerShell
Invoke-WebRequest -Uri "https://api.ipify.org" -UseBasicParsing | Select-Object -ExpandProperty Content

Compare the result to your router's WAN IP. On a Telus residential connection they will always be different — confirming CGNAT.

There are actually two layers of NAT between your laptop and the public internet:

The three-level address stack
True public IP 142.x.x.x ← owned by Telus, on their CGNAT device, shared by many customers Router WAN IP 100.72.x.x ← RFC 6598 shared address space, invisible to public internet Laptop LAN IP 192.168.1.x ← RFC 1918 private, invisible even to Telus

cloudflared's outbound TCP connection crosses both NAT boundaries going out, both NAT devices record the flow in their state tables, and from that point forward the established connection is bidirectional. The two-NAT problem simply does not exist for outbound-initiated persistent connections.

Q 08

All traffic from Cloudflare treated as return traffic — the logbook entry

Once cloudflared initiates an outbound connection to Cloudflare, all the traffic from Cloudflare to cloudflared will be treated as responses and will be allowed by Telus because there is an entry in its logbook, am I right?

Yes, exactly right. And there is one important precision to add to your model: the traffic from Cloudflare is not technically a "response" in the HTTP sense — it can carry new visitor requests arriving minutes or hours after the original TCP handshake. But from the NAT devices' perspective at the TCP layer, it is always return traffic on an established connection, which is indistinguishable from a genuine response.

Cloudflare is exploiting the fact that TCP's return path is always allowed, and cloudflared's tunnel rides that return path permanently. The NAT devices only ever see the outer TCP layer, and that layer says: "established — allow it."

The content is completely opaque to them. They do not know or care that what is flowing through is a visitor's HTTP request being proxied from Japan, Canada, or anywhere else. All they see is: "a packet arrived from a known destination on a known port matching an established TCP flow — pass it through."

Q 09

Correcting the bidirectional flow diagram

Any adjustments on this bidirectional flow: Visitor — HTTP Connection — Cloudflare — TCP connection — cloudflared — HTTP Connection — IIS?

Three adjustments needed. The sequence of entities is correct. The labels on each leg need updating:

Corrected flow
Visitor ←— HTTPS —→ Cloudflare ←— HTTP/2 over TCP (encrypted tunnel) —→ cloudflared ←— HTTPS —→ IIS

Adjustment ① and ③ — both end legs are HTTPS, not HTTP

The visitor's browser always connects to Cloudflare over HTTPS — that is the padlock the visitor sees, issued by Cloudflare's certificate. On the other side, in this setup IIS forces an HTTP→HTTPS redirect, so cloudflared connects to localhost:443 with noTLSVerify: true to accept the self-signed certificate.

Adjustment ② — the middle leg is not just "TCP connection"

TCP is only the transport layer underneath. What rides on top of TCP is HTTP/2 — the protocol cloudflared uses to multiplex multiple visitor requests over the 4 persistent connections. On top of that, the tunnel has its own TLS encryption. So more precisely: HTTP/2 frames, encrypted with TLS, transported over TCP.

Important consequence

Because there are three separate TLS sessions, Cloudflare is not a dumb pipe — it fully decrypts the visitor's request at its edge in order to read the Host header, apply security rules, and decide which tunnel to route it through. It then re-encrypts the request for the tunnel leg. This means you are trusting Cloudflare to see your plaintext traffic. The payoff is DDoS protection and a valid public certificate.

Q 10

The three TLS sessions and what HTTP/2 is

Can you state again on what the three TLS sessions are and explain what HTTP/2 is and its difference from HTTP?

Session 1 — Visitor to Cloudflare

The visitor's browser opens a standard HTTPS connection to Cloudflare's servers. Cloudflare presents its own TLS certificate — the padlock the visitor sees. Cloudflare fully decrypts this traffic at its edge. This is where Cloudflare reads the Host header to decide which tunnel to route the request through.

Session 2 — Cloudflare to cloudflared

The tunnel pipe itself is encrypted with a separate TLS session using a Cloudflare-issued origin certificate. This protects the data travelling between Cloudflare's edge and your laptop. cloudflared initiated this session outbound in Phase 1 and keeps it alive permanently.

Session 3 — cloudflared to IIS

cloudflared makes a fresh HTTPS connection to localhost:443 on your laptop. IIS presents a self-signed certificate — which is why noTLSVerify: true is in your config.yml, telling cloudflared to accept it without checking who signed it. This session is entirely local, never leaves the machine.

Three separate TLS sessions
Visitor Cloudflare decrypts both cloudflared IIS TLS Session 1 Cloudflare cert TLS Session 2 origin cert TLS 3 self-signed

HTTP vs HTTP/2

HTTP/1.1 works like a single-lane road with a rule that only one car can travel at a time. You send a request, wait for the full response, then send the next request. Browsers worked around this by opening six or more parallel TCP connections — but each had its own overhead.

HTTP/2 works like a motorway. One single TCP connection stays open, and inside it you can send hundreds of requests simultaneously as independent numbered streams. The server sends back responses in whichever order it finishes — responses do not need to arrive in the order requests were sent. This is called multiplexing.

For cloudflared specifically, HTTP/2 means all four tunnel connections and all visitor requests ride inside one persistent TCP pipe. That single TCP pipe is what CGNAT sees, writes one state entry for, and keeps alive.

Q 11

Correction — HTTP/1.1 would not actually cause CGNAT problems

I don't understand about "HTTP/1.1 would have fractured this into many short-lived connections" because if cloudflared initiates the connection to the 4 edge servers through HTTP/1.1, then these connections will still have 30+ mins by default to get time out, right? Also cloudflared will initiate some traffic when it is idle, so these connections will never get timeout theoretically, am I right?

You are completely right on both points. That statement was wrong and needs correcting.

Correction If cloudflared used HTTP/1.1 for its 4 tunnel connections, those 4 connections would still be TCP, still be initiated outbound, still have 30+ minute CGNAT idle timers, and cloudflared would still send keepalives to prevent them from ever going idle. The CGNAT problem would not exist. The tunnel would survive just as reliably.

What HTTP/2 actually improves over HTTP/1.1 — the real difference

The genuine difference is about concurrency inside each connection, not about CGNAT survival.

With HTTP/1.1, each of the 4 connections can carry only one visitor request at a time. If 10 visitors arrive simultaneously, visitors 5 through 10 queue and wait. Maximum concurrency: 4 at a time.

With HTTP/2, each connection carries unlimited streams simultaneously. All 10 visitors are served in parallel without queuing. Maximum concurrency: effectively unlimited.

Why cloudflared uses QUIC in the first place then?

QUIC takes HTTP/2's stream multiplexing but builds it over UDP. The theoretical advantage: in HTTP/2 over TCP, one packet loss stalls all streams on that connection because TCP guarantees ordered delivery. In QUIC over UDP, one packet loss only stalls the affected stream — the others continue. In practice on a stable connection this advantage is negligible, which is why switching to HTTP/2 solved the CGNAT problem with no noticeable performance cost.

Q 12

Why 4 connections — and can it be adjusted for busier traffic?

Why cloudflared opens 4 connections to edge servers? Can I adjust it if the traffic is busier?

Why exactly 4

The number 4 is hardcoded by Cloudflare's design, not configurable by the user. The reasoning behind it is resilience, not throughput. Each of the 4 connections goes to a different Cloudflare edge server. If one edge server has a problem — hardware fault, network blip, software crash — the other 3 connections keep the tunnel alive without any interruption visible to visitors. Cloudflare's edge automatically reroutes incoming traffic to whichever connections are healthy.

Can you adjust it for busier traffic?

No — and more connections would not help throughput anyway. The bottleneck in this setup is never the tunnel connections. It is one of two other things: either your laptop's CPU and RAM serving IIS responses, or your home internet upload bandwidth delivering those responses back through the tunnel.

Adding a 5th or 6th connection to Cloudflare's edge does nothing to either of those constraints. HTTP/2 already lets you pack unlimited visitor streams into each of the 4 lanes. Adding lanes when the lanes are not the bottleneck is pointless.

Real bottleneckCorrect fix
IIS slow to respondAdd caching, optimise the app, increase IIS thread pool
Upload bandwidth saturatedUpgrade Telus plan for more upload capacity
Single machine is the limitRun cloudflared on multiple machines — each creates its own 4-connection tunnel, Cloudflare load-balances (paid feature)
Route different traffic differentlyUse cloudflared ingress rules to route different paths to different local services
Practical ceiling For a personal or small business site like GuyStore, the 4 connections with HTTP/2 multiplexing can comfortably handle hundreds of simultaneous visitors before the tunnel itself becomes the constraint. The real ceiling will be the laptop and Telus upload speed long before running out of tunnel capacity.