Skip to content
Chapter 10Lesson 1

The four network stages of a request

The four stages a request crosses from URL to first byte, on the HTTP/3-over-QUIC stack, read in the DevTools Network panel.

A slow page load is rarely one problem. Between committing a URL and the first byte coming back, a request crosses four stages, and usually one is the bottleneck while the rest are fine. An engineer who reads a single waterfall row and names the slow stage starts in the right place; one who sees “the site is slow” and jumps to the server logs can lose half a day on something that was never the problem.

Those four stages are DNS, transport, TLS , and HTTP. Each gets its own section here, and a fifth covers the DevTools Network panel, where you’ll do most of your debugging.

Open DevTools now (Cmd+Opt+I on macOS, F12 on Windows or Linux), switch to the Network tab, right-click any column header, and turn on the Protocol column. You’ll use it mid-lesson.

Here’s the whole route a request takes, from the moment you commit a URL to the first byte of HTML.

%%{init: {'themeCSS': '.messageText, .messageText tspan, .actor, .actor tspan, .noteText, .noteText tspan, .labelText, .labelText tspan { font-size: 18px !important; } .loopText, .loopText tspan { font-size: 16px !important; }'} }%%
sequenceDiagram
    participant B as Browser
    participant R as Resolver
    participant S as Server

    rect rgba(56, 189, 248, 0.12)
        Note over B,S: Stage 1 — DNS · ~20-80 ms cold, ~0 ms warm
        B->>R: query example.com (A + AAAA)
        R-->>B: 2606:4700:: / 104.16....
        Note over B,R: Happy Eyeballs v2 races IPv6 and IPv4
    end

    rect rgba(34, 197, 94, 0.12)
        Note over B,S: Stages 2+3 — Transport + TLS folded into QUIC · 1 RTT fresh, 0 RTT resumed
        B->>S: Initial (ClientHello + QUIC frames)
        S-->>B: ServerHello + cert + handshake
        B->>S: Finished
        Note over B,S: HTTP/3 over QUIC on UDP/443 — TLS 1.3 folded in
    end

    rect rgba(244, 114, 182, 0.12)
        Note over B,S: Stage 4 — HTTP request · TTFB
        B->>S: GET / HTTP/3
        S-->>B: 200 + first byte
    end
Four stages from URL to first byte on the default stack: HTTP/3 over QUIC, TLS 1.3, and DoH where the resolver supports it.

The horizontal arrows are network round trips, and each one costs wall-clock time as a request travels to a remote machine and back. Stages 2 and 3 are drawn as one block because they are the same handshake: TLS 1.3 is folded into the QUIC transport setup, so the encrypted connection is built in one round of negotiation instead of two. Stage 4 is the HTTP request itself, the one that carries a method and a path; stages 1 through 3 are the cost of getting ready to send it. To debug a slow page, check those setup stages first: on a warm connection they collapse to near zero, as the next four sections show.

The browser has a hostname like app.example.com, but the network stack can’t open a connection to a name. It needs an IP address, and producing one from the other is what DNS does.

The browser hands the hostname to the OS resolver, which walks a chain of caches: the browser’s own cache first, then the OS cache, then the recursive resolver your network is configured to use (your ISP’s, or Cloudflare’s or Google’s), and finally the authoritative server for the domain. A cold lookup, with nothing cached anywhere, typically costs 20 to 80 milliseconds; a warm one costs effectively zero.

That resolver conversation is now usually encrypted. The standard form is DoH . Firefox enables it by default in most regions, and Chrome and Edge auto-upgrade to it when the system resolver advertises support, falling back to plain DNS only when nothing better is available. Two other encrypted forms exist, DoT (DNS over TLS) and DoQ (DNS over QUIC), but DoH is the production reality the course assumes.

The browser doesn’t issue one lookup, it issues two and races them: an A record query for IPv4 and an AAAA query for IPv6. The algorithm is Happy Eyeballs v2 . When both families resolve in time it prefers IPv6, but it never waits on it: if v6 is broken, v4 wins and the user notices nothing. Without this race, a misconfigured IPv6 path would stall the page for the resolver’s full timeout.

With an IP address in hand, the browser opens a connection. By default it reaches for QUIC on UDP port 443, carrying HTTP/3.

HTTP/2 multiplexes several requests onto one connection, but it does so at the HTTP layer, over TCP. TCP delivers bytes strictly in order, so if one packet drops, every later packet waits for it, even packets belonging to a different stream. That stall is head-of-line blocking, and on a lossy mobile network it tanks throughput. QUIC moves multiplexing into the transport itself: each stream keeps its own state, so a lost packet holds up only its own stream. QUIC also folds in TLS 1.3 (Stage 3), encrypting even the transport metadata.

The browser doesn’t assume HTTP/3 for a new origin. It opens the first connection over HTTP/2, reads the Alt-Svc response header advertising HTTP/3, and upgrades on the next request. A newer path, the HTTPS DNS record (type 65 / SVCB), carries the same hint in the DNS lookup itself, but Alt-Svc is still the common case in 2026. You’ll see the consequence in the DevTools drill at the end of this lesson: the first request to a fresh origin shows h2, and the next navigation flips to h3.

The fallback path still matters, because HTTP/2 over TCP with TLS 1.3 is everywhere. Networks that block or throttle UDP push clients back to HTTP/2 silently, and origins that haven’t upgraded still serve HTTP/1.1. The course writes no HTTP/1.1-specific code.

QUIC earns the switch on the RTT count it takes to reach the first byte. Three cases set side by side:

Client -> Server : Initial + ClientHello + (early HTTP request frames)
Server -> Client : ServerHello + cert + handshake + Finished
Client -> Server : Finished + HTTP request body
(continues...)

One round trip completes the handshake, then the request goes out. With no session ticket yet, TLS 1.3 inside QUIC still needs only one round trip to establish keys. Counting the response, the first byte arrives in about two round trips.

The number to remember: fresh QUIC is 1 RTT, resumed QUIC is 0 RTT, TCP plus TLS is 2 RTT. On a mobile connection where one round trip can run 100 ms or more, that gap decides whether the page feels snappy.

Stage 3: TLS 1.3 inside the QUIC handshake

Section titled “Stage 3: TLS 1.3 inside the QUIC handshake”

This stage is short by design: cipher suites, certificate chains, SNI, and ALPN are the subject of the HTTPS on localhost lesson later in this chapter. Four facts are enough to read the diagram and the round-trip counts.

The 2026 stack speaks only TLS 1.3, and the browser refuses older protocols, so treat it as a given. In QUIC the TLS handshake is interleaved with the transport handshake rather than stacked on top: the ClientHello rides inside the very first QUIC packet. A fresh connection completes in one round trip; a resumed connection completes in zero, reusing a session ticket the client kept from the previous visit.

That zero-round-trip path carries a replay-safety constraint. On a resumed connection the client can put the request bytes, the early data, inside that first packet, but those bytes have no fresh nonce from the server yet, so an attacker who recorded the packet could replay it and the server could not tell the copy from the original. The fix is to send only idempotent GETs as early data; requests that change server state (POST, PATCH, DELETE) wait for the full handshake. The browser and the HTTP/3 stack enforce this for you, but the rule is worth knowing, because the next chapter builds the HTTP contract on idempotency as the property that makes safe retries possible.

TLS 1.3 also gives normal traffic forward secrecy: each session uses its own ephemeral keys, so if the server’s long-term private key leaks tomorrow, yesterday’s recorded sessions stay undecryptable. The exception is 0-RTT early data, which by construction rides on keys derived before that ephemeral exchange and so has no forward secrecy. That is one more reason early data is reserved for idempotent GETs.

Stage 4: The HTTP request and time to first byte

Section titled “Stage 4: The HTTP request and time to first byte”

Once the secure transport is up, the client sends what it came for: an HTTP/3 request carrying a method (GET), a path (/), a :authority pseudo-header naming the host, the request headers, and, for non-GET requests, a body. The server processes it, starts responding, and the first byte of the response body arrives.

The wall-clock time from committing the URL to that first byte landing is TTFB . It is one number on the stopwatch but not one thing: it bundles every stage above, DNS, connection setup, TLS handshake, request travel, server thinking time, and the round trip back.

The Network panel is your main debugging surface for the rest of this unit. Each row is one request, and it carries a Protocol label and a Timing breakdown that map onto the four stages above.

The Protocol column tells you which HTTP version the request used:

  • h3: HTTP/3 over QUIC.
  • h2: HTTP/2 over TCP and TLS. The fallback, and also the first hit to a fresh origin before Alt-Svc discovery.
  • http/1.1: origins that haven’t upgraded, or clients on networks that block UDP and fall back past both hints.

This is where the Alt-Svc nuance from Stage 2 surfaces. On a fresh origin the first document request can show h2; reload and the document row flips to h3, because the browser has now learned HTTP/3 is available. If a production origin still isn’t h3 on a second load, that’s a CDN configuration issue, not a bug in your code.

Click a row, switch to the Timing tab, and you’ll see a stacked bar. Each segment maps to a stage:

  • Queued / Stalled: the browser’s own bookkeeping , waiting on a connection slot or priority queue.
  • DNS Lookup: Stage 1. Often 0 ms on a warm cache.
  • Initial connection: Stage 2. For h3, the QUIC handshake collapses into this segment; for h2, it’s the TCP handshake.
  • SSL: Stage 3. For h3 it’s folded into Initial connection and usually has no separate bar; for h2 it’s the TLS round trip on top of TCP.
  • Request sent: bytes uploaded, usually tiny for a GET.
  • Waiting (TTFB): Stage 4. Server thinking time plus the round trip back.
  • Content Download: the response body streaming in.

Now do it on a real site, against cloudflare.com (always h3 on a modern browser) or this course’s site.

  1. In DevTools, switch to the Network tab. In the toolbar, tick Disable cache. This forces a real network fetch instead of a cached response.

  2. Hard-reload the page (Cmd+Shift+R on macOS, Ctrl+Shift+R on Windows or Linux). Watch the document row, the first row, the one for the page’s HTML.

  3. Check the Protocol column. On the first load to a fresh origin you may see h2; reload once more and the document row flips to h3, since the browser learned about HTTP/3 from the Alt-Svc header on the first response. Click the document row, open the Timing tab, and find the DNS Lookup, Initial connection, Waiting (TTFB), and Content Download segments. On h3, the SSL bar collapses into Initial connection.

  4. Untick Disable cache and do a soft reload (Cmd+R or F5). DNS Lookup and Initial connection should collapse to near zero: the DNS is cached and the QUIC connection is still alive. That’s a resumed connection, made visible.

  5. Click the Waiting (TTFB) segment and read its number. That’s the server-and-network cost in isolation, with the setup costs already paid by earlier requests.

LocalQueuedStalledDNS · 0 msInitial connectionQUIC + TLS foldedReqWaiting (TTFB)Content DownloadStage 1DNS resolution(warm cache)Stages 2 + 3QUIC handshakeTLS 1.3 folded inStage 4HTTP request + server= TTFBResponse bodybytes streaming inOn h3 the SSL bar is folded into Initial connection, so there is no separate TLS row.DNS Lookup is a 0 ms sliver because this row’s resolver hit a warm cache.
A schematic h3 document request. Initial connection holds the QUIC and TLS handshake (Stages 2 and 3 folded), Waiting (TTFB) is Stage 4, and DNS Lookup is 0 ms on a warm cache. SSL never appears as its own bar.

Match each waterfall row to its dominant stage

Section titled “Match each waterfall row to its dominant stage”

Reading a waterfall means naming which stage dominates a row’s time, the bottleneck you would target first. Match each row’s shorthand to its dominant stage.

Match each waterfall row shorthand to the stage that dominates its wall-clock time. Click an item on the left, then its match on the right. Press Check when done.

h3 · DNS 0 ms · Initial connection 0 ms · Waiting (TTFB) 320 ms · Content Download 12 ms
Server-bound — warm connection, the server is thinking
h2 · DNS 75 ms · Initial connection 90 ms · SSL 80 ms · Waiting (TTFB) 60 ms · Content Download 8 ms
Connection-bound — cold TCP + TLS handshake dominates
h3 · Queued 0 ms · DNS 60 ms · Initial connection 35 ms · Waiting (TTFB) 50 ms
DNS-bound — cold lookup dominates
h3 · Queued 280 ms · DNS 0 ms · Initial connection 0 ms · Waiting 40 ms
Queue-bound — browser priority queue or connection-pool stall
h3 · Waiting (TTFB) 30 ms · Content Download 1800 ms
Content-bound — large response body, server was fast
(from disk cache) · 0 ms total
Cached — no network leg at all, served from the browser’s HTTP cache

Name the dominant stage before you debug: it tells you where the time went, so you fix the right leg instead of guessing. The same read works on any row you meet in production.