The URL Journey: What Happens When You Press Enter

Five systems, one address bar. This module composes the DNS, TCP, TLS and HTTP modules into a single timeline so you can see where time to first byte actually goes — and which of the phases a cache hit, a reused connection or a CDN edge makes disappear entirely. The lesson is subtraction: most performance work is not making a phase faster, it is arranging for the phase not to happen.

Launch Simulator →

What you'll explore

  1. 01

    Nobody's Cache Is Where You Think

    Before a single packet can be addressed to the server, a name has to become an address. The surprise is where that answer is kept. The stub resolver's send path — the code your process actually runs to ask a question — holds no answer cache at all: it holds a list of nameservers and a retry loop, and every lookup that reaches it goes out on the wire. (A local caching daemon such as nscd or systemd-resolved, when one is running, sits in front of that path and can answer without it; the send path itself never caches.) The cache that makes repeat lookups free normally lives one hop away, in the recursive resolver, and it is shared with everyone else using it. That has two consequences worth internalising: a 'cached' lookup still costs you the round trip to the resolver, and a cache hit is not automatically a fresh answer — a resolver under load is permitted to hand back an expired record rather than make you wait for the authoritative one.

  2. 02

    One Exchange, Paid Before Anything Is Said

    The transport handshake buys nothing except the right to send. No HTTP byte, no TLS byte, nothing about the page — one full round trip spent establishing that both ends exist and agree on sequence numbers. What the kernel source makes vivid is the ordering: the socket is moved into SYN_SENT before the SYN packet has been built, because the state machine is the authority and the packet is merely its consequence. Once the SYN goes out a retransmit timer is armed, which is why a lost SYN is the most expensive packet loss on the internet: there is no data in flight to trigger a fast retransmit, so recovery waits out a full timeout.

  3. 03

    The Round Trip You Can Arrange Not To Pay

    TLS 1.3 costs one round trip on a fresh connection because the client's very first message already carries a key share — the server can answer with its own share, its certificate and its Finished in a single flight. TLS 1.2 needs two, because the key exchange cannot begin until the hello exchange has completed. Both numbers become zero on a connection that is already open, and the reference implementation makes that explicit in three lines near the top of its handshake entry point: if the connection is not still initialising, it returns success immediately. No wire activity, no negotiation, no cost. Session resumption offers a middle path, but it is opt-in in a way the marketing copy omits: the client only keeps the server's ticket if the application explicitly asked for a session cache and installed a callback to receive it.

  4. 04

    The Request Itself Is the Cheap Part

    After all that setup, the request is a few hundred bytes. What differs between the HTTP versions is not the request's size but how many of the phases above it still needs. HTTP/1.1 frames messages as text and, although RFC 9112 does permit pipelining several requests onto one connection, it requires the responses to come back in request order — so one slow response holds up everything queued behind it. That ordering rule, not a hard one-request-at-a-time limit, is what made pipelining unusable in practice and produced the era of six parallel connections and sharded domains. HTTP/2 keeps the same transport and multiplexes many streams over one connection. HTTP/3 changes the transport: QUIC performs the cryptographic handshake as part of establishing the connection, so what were two sequential exchanges become one. The layering is strict and worth respecting — the stream a request travels on is allocated by the QUIC transport, while the HTTP/3 library only frames onto a stream identifier the transport has already minted.

  5. 05

    Time to First Byte, Decomposed

    Time to first byte is the sum of everything above plus the server's own think time — and it is measured in round trips, not in bytes. That is the single most useful thing in this module: a 14 KB HTML document crosses a gigabit link in a fraction of a millisecond, so on a distant connection the bandwidth term is noise and the distance term is the whole story. Upgrading the pipe does not move it; removing an exchange does. On the client side the first byte surfaces when the receive path has bytes to copy to userspace, and for HTTP/2 those bytes are then fed frame by frame into a parser that resumes mid-frame across segment boundaries — a response split by the network is reassembled by state, not by waiting for a complete message.

Validated against glibc stub resolver + BIND 9 recursive resolver + Linux kernel TCP + BoringSSL + nghttp2 (HTTP/2) + nghttp3 (HTTP/3 framing) + ngtcp2 (QUIC transport) glibc-2.44 + bind-9.20.29 + v6.12.93 + boringssl-eada60b7 + nghttp2-1.70.0 + nghttp3-1.18.0 + ngtcp2-1.25.0 — resolv/res_send.c (glibc stub resolver) and friends. Every simulated behavior cites a real source function, and a server-side correlation engine computes how each layer's state cascades into the next. See the methodology →

This module is part of zerohop Pro — it unlocks alongside the full kata library, progress tracking, and weak-area analysis.