The Lifecycle of One HTTP Request
A fetch() becomes an HTTP message inside TLS records inside TCP segments inside IP packets, crosses the network, is unwrapped in reverse on the server, handled, and returns the same way — and on a cold connection most of the time is round trips, not work.
The problem
Down the stack and up again
A single fetch("https://api.example.com/users/42") passes through every layer in this domain. The application builds an HTTP message. If there is no warm connection to that origin, the runtime resolves the name (Following One Lookup Through Every Cache), opens a TCP connection (The Three-Way Handshake), runs a The TLS Handshake, and only then writes the request. TLS splits it into records; TCP splits records into segments sized to the MSS; IP wraps each in a packet with addresses; the link layer wraps each packet in a frame with MACs (Encapsulation: Data, Segment, Packet, Frame). Routers forward on the IP header only; nothing on the path needs to understand a byte above it.
On the server the sequence reverses. The NIC delivers frames, the kernel reassembles the TCP stream and wakes the process blocked in accept()/read() (Follow send() Through the OS to recv()), the TLS library decrypts records, the HTTP parser finds the end of headers, the framework routes /users/42 to a handler, the handler queries a database over *another* connection with its own lifecycle (Connection Pooling), and the response walks down the same stack. Each layer adds its own header bytes, and each has its own way to fail.
- Application`fetch()` — method, URL, headers, body; picks a connection from the pool or opens one↓
- DNS (cold only)Name → IP; 0 ms from cache, 1 RTT to the resolver, more if the resolver must recurse↓
- TCP (cold only)`SYN`/`SYN-ACK`/`ACK`: 1 RTT; initial `cwnd` ≈ 10 segments↓
- TLS (cold only)1 RTT (1.3) or 2 RTT (1.2); certificate verification; adds ~22–30 bytes per record↓
- HTTP message~300–800 bytes of headers (cookies dominate) + body↓
- TLS records≤ 16 kB plaintext each, encrypted + authenticated↓
- TCP segments20-byte header (+12 with timestamps); ≤ MSS ≈ 1460 bytes each↓
- IP packets20-byte IPv4 / 40-byte IPv6 header; routed hop by hop↓
- Ethernet frames14-byte header + 4-byte FCS; 1500-byte MTU↓
- Server: kernel → process → handler → responseUnwrap in reverse; the handler’s own time; response walks back down
A worked timeline on a 100 ms link
Take an RTT of 100 ms between client and server (roughly Frankfurt to New York), a resolver 20 ms away, TLS 1.3, a 5 ms handler and a 40 kB JSON response. The numbers below are an educational model — real measurements vary with the resolver cache state, TCP options, CDN presence and server load — but the proportions are what matter.
On a cold connection almost nothing is "work": it is one RTT after another, each of which cannot start before the previous completes. The 40 kB response also does not fit in TCP’s initial congestion window (10 segments ≈ 14 kB), so delivering it takes two extra round trips of slow-start growth (Congestion Control: Protecting the Network). On a warm, already-open connection the same request costs one RTT plus the handler plus whatever transfer the now-larger cwnd needs — about a third of the cold time.
COLD connection 0 ms DNS query → resolver (cache miss at the resolver: + recursion) ~20–120 ms 40 ms TCP SYN ──────────────────────────► SYN-ACK ◄────── ACK 100 ms 140 ms TLS 1.3 ClientHello ─────────────► ServerHello…Finished ◄── 100 ms 240 ms HTTP request ──────────────────────► handler 5 ms ── first byte 105 ms 345 ms first 14 kB (initial cwnd) arrive; ACKs go back; cwnd doubles 100 ms 445 ms remaining 26 kB arrive ~100 ms ≈ 545 ms response complete (≈ 5 ms of it was the handler) WARM connection (keep-alive, cwnd already grown) 0 ms HTTP request ──────────────────────► handler 5 ms ── first byte 105 ms 105 ms 40 kB arrive within one window ~ 10 ms ≈ 115 ms response complete
What each layer charges for, and what you can avoid
The bill splits into three kinds of cost. Setup (DNS, TCP, TLS) is paid once per connection and is pure latency: it disappears with connection reuse (Keep-Alive and Connection Reuse), DNS caching, TLS resumption, or QUIC’s combined handshake (HTTP/3 and QUIC). Transfer depends on bytes and cwnd: smaller responses, compression and a warm connection reduce it; bandwidth rarely matters for a 40 kB response but slow-start round trips do (Bandwidth vs Latency). Server time is the only part you can profile in the application, and on a cold connection it is a rounding error.
This is why "the server is idle and the request still takes 500 ms" is not a contradiction (Where the Time Goes: The Request Timeline) and why a CDN or a regional edge that terminates TLS 50 ms closer to the user and keeps warm connections to the origin can halve time-to-first-byte without touching the application. It is also why moving one service call from same-rack (0.2 ms RTT) to cross-region (80 ms RTT) can multiply an endpoint’s latency: every dependency has its own lifecycle, and they usually run in series.
- Cold HTTPS ≈ DNS + 1 RTT (TCP) + 1–2 RTT (TLS) + 1 RTT (request) + slow-start RTTs + server time.
- Warm HTTPS ≈ 1 RTT + server time (+ transfer if large).
- Every dependency (database, cache, another service) has the same shape, and their setup costs are why pools exist.
- Browser DevTools’ timing panel breaks a request into exactly these phases: Queueing, DNS, Initial connection, SSL, Request sent, Waiting (TTFB), Content download.
Key points
- A request is wrapped layer by layer on the way down (HTTP → TLS → TCP → IP → link) and unwrapped in reverse at the server; routers only read the IP layer.
- On a cold connection the cost is sequential round trips — DNS, TCP, TLS, request — and slow-start; the handler’s time is usually the smallest term.
- A warm connection removes DNS, TCP and TLS setup and starts with a grown congestion window: roughly a third of the cold cost in the model above.
- Each layer adds header bytes (≈ 20 TCP + 20/40 IP + 18 Ethernet + TLS record overhead) and a distinct failure mode.
- Every dependency call has the same lifecycle; series dependencies multiply latency, which is why pooling, locality and CDNs matter.
- Browser and server timing tools break a request into exactly these phases — learn to read them.
Why does this exist?
Mechanisms are answers to constraints. Open each question before reading the answer.
▸Why can the layers not be set up in parallel?
Each needs the previous one’s result: TCP needs the IP from DNS, TLS needs a byte stream from TCP, HTTP needs keys from TLS. QUIC’s only trick is to merge TCP’s and TLS’s handshakes into one because it owns both — and browsers do speculatively pre-resolve and pre-connect to hosts they expect to need.
▸Why does a 40 kB response take three round trips instead of one?
TCP starts a new connection with a small congestion window (about 10 segments, ~14 kB) and doubles it per RTT; a response larger than the window waits for acknowledgements before the rest can be sent. Warm connections have already grown their window.
▸Why does the server look idle while the user waits?
Because it is. During DNS, handshakes and in-flight transfer the server process is blocked in accept() or has already written the response to the socket buffer; the time is on the wire, not on the CPU.
How it fails
What the failure looks like from inside real software.
- p99 latency dominated by cold connections: a client with keep-alive disabled (or an agent pool sized at 1) pays DNS + TCP + TLS on every request.
- DNS resolver slow or timing out: every cold request stalls seconds before TCP even starts, and the server sees nothing.
- Cross-region dependency introduced "temporarily": each request now serialises an 80 ms RTT per call and the endpoint’s latency doubles.
- Large cookies: 4 kB of headers on every request exceeds the initial window, so even the request takes two round trips on a cold connection.
- A 5-second handler behind a proxy with a 3-second timeout: the client gets 504, the handler finishes anyway, and the work is done twice on retry.
Follow it through every layer
This lesson is one node of a longer journey. Zoom out, then zoom back in.