Follow one web request from the URL bar to the response: DNS, TCP, TLS, HTTP versions, CDN, load balancer, app and database, plus where the time goes and how sessions work.
What you'll learn: By the end of this lesson you will be able to trace what happens between typing a URL and seeing a page, step by step. You will be able to explain how HTTP/1.1, HTTP/2 and HTTP/3 differ, say where a request spends its time, and describe the difference between stateless and stateful servers and how sessions and tokens fit in.
Sending a letter looks simple from the outside: write, stamp, post. Inside, it passes through a local post box, a sorting office, a truck, another sorting office and a delivery round, and each hand-off adds time and a chance of loss. If you wanted the letter to arrive faster, you would have to know which hand-off is the slow one.
A web request is a letter with a dozen hand-offs. You cannot make a system fast or reliable until you know what those hand-offs are. This lesson follows one request, step by step, using a shop's checkout page as the example. Later lessons on load balancing, caching and CDNs zoom in on individual steps.
The core idea
When you open https://shop.example.com/checkout, the browser knows only a name. To get a page it must:
Find out where the server is (DNS).
Open a reliable connection to it (TCP).
Make that connection private (TLS).
Ask for the page (HTTP).
Let the server side work out the answer, passing through edge, load balancer, application, cache and database.
Receive and render the response.
Steps 1 to 3 are overhead that happen before a single byte of your page is requested. Each can cost a whole round trip time (RTT): the time for a message to travel to the server and for the reply to come back. On a nearby server an RTT is a few milliseconds. To a server on another continent it is easily 100 milliseconds. Since each setup step costs whole round trips, distance multiplies cost, and much of web performance engineering is about doing fewer round trips.
The whole journey in one picture
sequenceDiagram
participant B as Browser
participant R as DNS resolver
participant E as CDN edge
participant L as Load balancer
participant A as App server
participant C as Cache
participant D as Database
B->>R: Where is shop.example.com
R-->>B: IP address of the edge
B->>E: TCP handshake then TLS handshake
B->>E: GET /checkout
E->>L: Cache miss so forward to origin
L->>A: Pick a healthy instance
A->>C: Look up cart
C-->>A: Miss
A->>D: Query cart rows
D-->>A: Rows
A->>C: Store result
A-->>L: Response
L-->>E: Response
E-->>B: Response
How to read it: time runs downward. Everything above the GET is setup cost. Everything between the edge and the database is work only the first request pays if the cache and CDN are doing their job.
Step 1: DNS, the phone book
DNS (Domain Name System) translates a name into an IP address. Your browser asks a recursive resolver, usually run by your internet provider or a public DNS service. If it does not already know the answer, it works down a hierarchy: a root server says who handles .com, the .com servers say who is responsible for example.com, and that domain's authoritative server gives the final record.
Every answer carries a TTL (time to live): how many seconds it may be cached, at the browser, the operating system and the resolver. Most lookups therefore never walk the hierarchy. A cold lookup may cost tens of milliseconds, a cached one almost nothing.
The TTL is a design lever. A long TTL (hours) reduces lookups and survives short DNS outages but makes changes slow to spread. A short TTL (a minute) lets you move traffic quickly in a failover but increases lookups. Many teams lower the TTL before a planned migration, then raise it again.
NOTE
DNS is also a basic traffic-steering tool. An answer can differ depending on where the asker is, pointing users to a nearby data centre or edge. We return to this in the load-balancing and multi-region lessons.
Step 2: TCP, a reliable connection
Most web traffic runs over TCP (Transmission Control Protocol), which gives an ordered, reliable stream of bytes on top of an unreliable network. Before data flows the two sides perform a three-way handshake: the client sends a SYN, the server answers SYN-ACK, the client replies ACK. That costs one round trip before the client can send data. TCP also resends lost packets and slows down under congestion. The price is waiting: if one packet is lost, later data on that connection waits for the missing piece. Remember that for HTTP/3.
Step 3: TLS, making it private
TLS (Transport Layer Security) encrypts the connection and lets the browser verify, through a certificate, that it is really talking to shop.example.com. It is the "S" in HTTPS. With TLS 1.3 the handshake needs about one extra round trip; older TLS 1.2 needs about two; resuming a recent session can be quicker still.
So on a brand-new connection, before the first request, the client has paid roughly a DNS lookup, one RTT for TCP and one for TLS 1.3. Opening a fresh connection per request would be very costly. The next section shows how connections are kept.
stateDiagram-v2
[*] --> Lookup
Lookup --> Connecting: DNS answered
Connecting --> Secure: TCP and TLS done
Secure --> Active: Request sent
Active --> Idle: Response complete
Idle --> Active: Reused via keep-alive
Idle --> Closed: Idle timeout
Closed --> [*]
How to read it: the cost sits in the first three states. A connection that loops between Active and Idle pays that cost once. One that goes to Closed after every response pays it every time.
Step 4: HTTP, asking for the page
HTTP is the language of the request. A request has a method (GET), a path (/checkout), headers (accepted languages, cookies) and sometimes a body. The response has a status code (200 OK), headers and a body.
Version
Key idea
Strength
Weakness
HTTP/1.1
One request at a time per connection; connections can be kept alive and reused
Simple and universal
A slow response blocks those behind it; browsers open several connections to compensate
HTTP/2
Many requests share one connection at once (multiplexing); headers are compressed
Far fewer connections, faster pages with many assets
Still on TCP, so one lost packet stalls every stream
HTTP/3
Runs over QUIC (built on UDP); streams are independent; transport and encryption are set up together
A lost packet delays only its own stream; faster setup; handles network changes such as wifi to mobile
Newer; some networks block UDP, so clients fall back to HTTP/2
In everyday terms: HTTP/1.1 is a single-lane road, HTTP/2 a multi-lane road with one shared gate, and HTTP/3 gives each lane its own gate.
Keep-alive means the connection stays open after a response so the next request skips DNS, TCP and TLS. It is a quiet but huge win, and the same idea applies between servers: an application keeps a pool of open connections to its database, because opening one per request is slow (see Databases Basics). Set sensible idle timeouts so a half-dead connection does not linger.
Step 5: edge, load balancer, application
CDN edge. If the URL points to something cacheable (an image, a script, perhaps a public page), the nearest edge server answers immediately without touching your servers, which is the cheapest and fastest outcome. A personalised page usually passes through to the origin (your own servers behind the CDN).
Load balancer. It chooses one of several application instances, skipping any that fail health checks. See Load Balancing. It often terminates TLS, meaning it decrypts traffic so the instances do not have to.
Application. The instance authenticates the caller, validates the request and builds the answer. It checks the cache first (typically around a millisecond on a hit), otherwise queries the database, stores the result and returns. Slow work, such as sending an email, goes on a queue so the response is immediate.
Response. It travels back the same way. Headers such as Cache-Control tell the browser and CDN how long each piece may be reused.
Where the time goes
Here is a rough timeline for a cold visit by a user far from the server. Every number is an illustrative assumption: 80 ms round trip to the origin, no CDN in the path.
Phase
Cost
Illustrative time
DNS lookup (uncached)
resolver lookup
25 ms
TCP handshake
1 RTT
80 ms
TLS 1.3 handshake
1 RTT
80 ms
HTTP request and response (network)
1 RTT
80 ms
Server work (cache hit or database query)
n/a
5 to 30 ms
Total, first request
about 270 to 300 ms
A warm visit, with DNS cached and the connection reused, skips the first three rows and takes roughly 85 to 110 ms. Put the same content on a CDN edge 10 ms away and each round trip costs 10 ms instead of 80. In this example the server's own work is the smallest piece. That is typical: for distant users, network round trips often dominate, not your code. It is also why ten sequential browser calls, where one would do, feel slow.
TIP
Before optimising, measure. Browser developer tools show the timing of every phase (DNS, connect, TLS, waiting, download). Fix the biggest slice first.
Stateless versus stateful servers
A stateful server remembers things about earlier requests in its own memory, such as "this user is logged in". A stateless server keeps nothing between requests: everything it needs arrives with the request or lives in a shared store.
Why it matters: with ten instances behind a load balancer, if instance 3 holds your login in memory and the next request goes to instance 7, you appear logged out. With stateless servers any instance can handle any request, so you can add, remove or replace instances freely.
Approach
How it works
Benefit
Cost
Stateful server
Session data in server memory
Simple for one server
Breaks when scaled out or restarted; forces sticky routing
Stateless with shared store
Session data in Redis or a database
Easy to scale the app tier; instant revocation
An extra lookup per request; the store must be reliable
Stateless with tokens
Identity carried in a signed token
No lookup on most requests
Harder to revoke before expiry
HTTP has no memory, so sessions are built on top. A cookie is a small value the server asks the browser to store and send back. A session is the server's record of who you are: the cookie holds a random ID, and the server looks it up, so it can revoke it instantly. A token (such as a JSON Web Token, JWT) carries the claims itself, signed, so no lookup is needed, but you cannot easily cancel it before expiry without a deny-list or short lifetimes. Mark cookies Secure, HttpOnly and set a sensible SameSite. The Security lesson covers tokens in depth.
In practice: the checkout page is slow in one region only
NOTE
A realistic scenario, not a case history. This is a composite example built from common production patterns. It does not describe any specific company, and every number in it is an illustrative assumption.
The situation
An online retailer's checkout page feels fine in the home region and slow in one distant region. Support reports "checkout takes forever" from that region, and the dashboard shows the checkout conversion rate lower there. Assume the page is interactive in about 2 seconds for most users and about 4 seconds in the distant region, which is more than the extra distance alone should cost. Three engineers own the web tier. The stack: managed DNS, a CDN, Nginx ingress on Kubernetes in a single origin region, PostgreSQL, and Redis for sessions. Assume the round trip from the distant region to the origin is about 220 ms.
Starting architecture
How to read it: the edge is close to the shopper, but every request is a miss and travels the long way to the single origin.
Options considered
The team first needs to find out which hand-off is slow. This decision flow is the diagnosis they use.
flowchart TD
S["One region is slow"] --> Q1{"Is DNS lookup time high from that region?"}
Q1 -->|yes| F1["Check resolver and GeoDNS answers and TTL"]
Q1 -->|no| Q2{"Is connect plus TLS time high?"}
Q2 -->|yes| F2["Check edge location, TLS version and certificate chain"]
Q2 -->|no| Q3{"Does the CDN report a cache MISS?"}
Q3 -->|yes| F3["Check Cache-Control, Vary and Set-Cookie headers"]
Q3 -->|no| Q4{"Is time to first byte high on a HIT?"}
Q4 -->|yes| F4["Check edge to origin path and origin time"]
Q4 -->|no| F5["Look at the browser, JavaScript and payload size"]
How to read it: a few curl timing variables (time_namelookup, time_connect, time_appconnect, time_starttransfer), run from a probe in the region, answer the first two questions. The CDN's cache-status response header answers the third.
Option
What it fixes
What it costs
Risk
Do nothing, or add origin pods
Nothing for a network-bound problem
Cheap
More pods near the origin do not shorten a 220 ms round trip
Make static assets cacheable at the edge and keep the HTML dynamic
Removes most origin round trips for distant users
Header and build changes, a few days
Stale assets if versioning is wrong
Add a second origin region in the distant geography
Shortens every dynamic call
A second region of app, Redis and a database replica; a large cost and a data-residency question
Complexity far beyond a header bug
The probes show DNS and TLS are fine. The cache status is MISS for the JavaScript and CSS, because the framework adds Set-Cookie and Vary: Cookie to static files. Every asset crosses the world, and assets that depend on one another load in sequence.
The decision
Field
Record
Context
Static files marked uncacheable by accident. Distant users pay about 220 ms per sequential origin call.
Decision
Serve fingerprinted assets (for example app.3f9c.js) with Cache-Control: public, max-age=31536000, immutable, strip cookies from them, and keep the HTML dynamic (no edge caching, or only a few seconds). Enable origin keep-alive tuning so the CDN reuses connections.
Consequences
Good: cached assets cost one short round trip to the edge. Bad: the deploy process must upload new assets before switching HTML, and old assets must be kept for a while.
Revisit when
Dynamic API calls from the distant region are still the largest slice after the fix, which would justify a regional origin.
The saving, illustratively. Before, a chain of dependent calls: HTML, then CSS, then the JS bundle, then a lazy-loaded chunk, then the cart API, then the payment-config call, each about 280 ms (220 ms round trip plus about 60 ms of work), roughly 1.7 seconds of waiting on origin round trips. After: the CSS, bundle and chunk come from the edge at about 15 ms each, so the chain is roughly 280 + 15 + 15 + 15 + 280 + 280, close to 885 ms, a saving of about 0.8 seconds, and warm repeat visits skip even more.
Rollout and verification
flowchart LR
P["Probe from the region before"] --> C["Change headers for one asset path"]
C --> W["Watch cache hit ratio and TTFB"]
W --> R["Roll to all assets"]
R --> V["Probe again and compare"]
Change one asset path first, then all of them. Watch the CDN cache hit ratio by region, time to first byte by region (from real-user measurement or synthetic probes in that region), the origin request rate (it should fall) and checkout conversion by region. The target, not a promise: asset cache hit ratio above 95% in the region and the page interactive within about 3.2 seconds there (down from about 4).
The 2 a.m. failure
During the 02:00 release window the checkout page goes blank for some users. The page says "checkout asset 404 rate above 5%". The cause: the deploy deleted last release's assets, but the edge still has the previous HTML cached for a few minutes, and that HTML points at the deleted files.
sequenceDiagram
participant B as Browser
participant E as CDN edge
participant O as Origin
B->>E: GET checkout page
E-->>B: Cached HTML naming app.OLD.js
B->>E: GET app.OLD.js
E->>O: Cache miss so ask origin
O-->>E: 404 deleted by the deploy
E-->>B: 404 and a blank page
First three checks: curl -sI https://shop.example.com/assets/app.OLD.js for status and cache status, the CDN log filtered to status 404 by path, and the Age header on the HTML to see how stale it is. Mitigation: purge the HTML cache or restore the previous assets from object storage. Follow-up: upload new assets before switching the HTML, keep the last two releases' assets, and give the HTML a very short edge TTL.
Architect's notes
Measure by phase and by region before deciding. One curl timing breakdown from a probe in the affected region usually names the guilty hop in minutes.
Look for accidental uncacheability.Set-Cookie, Vary: Cookie and Cache-Control: no-store on static files are common and invisible from the home region.
Ask in the review: how many sequential round trips does this page need? The count times the RTT is the floor. Parallelise or remove calls.
Own the headers. Somebody must own cache headers and CDN rules, and releases must keep old assets alive.
Defer a second region. It fixes dynamic latency but multiplies cost, data and operations. Try caching and connection reuse first.
Common mistakes
Ignoring distance. All servers in one region serving the world adds hundreds of milliseconds per round trip. Fix: a CDN and, when needed, regional deployments.
New connections for every call. Skipping reuse repeats DNS, TCP and TLS. Fix: keep-alive and connection pools.
Chatty clients. Many small sequential requests multiply round trips. Fix: batch, combine endpoints, or use HTTP/2.
Sessions in server memory while scaling out. Users get logged out at random. Fix: a shared store or tokens.
Forgetting DNS TTL in a migration. Some users stay on the old address. Fix: lower the TTL ahead of time.
No timeouts at any hop. One slow hop pins resources back to the user. Fix: explicit connect and read timeouts at each hop.
Assuming the network is reliable. Packets are lost and routes change. Fix: retries on idempotent operations (safe to repeat) and failure handling.
Key takeaways
A request is a chain of hand-offs. DNS, TCP, TLS, HTTP, edge, load balancer, application, cache and database each add time and can fail.
Round trips are the unit of cost. Setup steps cost whole round trips, and distance multiplies them.
DNS caching is governed by TTL. Long TTLs reduce lookups but slow changes.
Reuse connections. Keep-alive and pools avoid repeating handshakes.
HTTP versions solve different blocking problems. HTTP/2 multiplexes on one TCP connection; HTTP/3 uses QUIC so a lost packet only slows its own stream.
For distant users the network dominates. CDNs and fewer round trips help most.
Stateless servers scale. Keep session state in a shared store or signed tokens.
Diagnose by phase. Timing breakdowns and cache-status headers localise a slow region fast.
Practice exercises
Exercise 1 — Starter (30 min): Open your browser's developer tools on any website, go to the Network tab, and reload with the cache disabled. For the main document request, write down the time spent in DNS, connecting, TLS, waiting and downloading. Which phase is largest? Then reload normally and note what changed.
Exercise 2 — Intermediate (2 hours): Using the timeline table, assume the round trip to the origin is 120 ms and to a CDN edge is 15 ms. Calculate the total for a cold first request to the origin and for a cold request answered at the edge (assume DNS 25 ms, TCP and TLS one round trip each, one round trip for the request, and 20 ms of server work at the origin or 2 ms at the edge). Then calculate the warm-connection times. Write one paragraph on where to invest.
Exercise 3 — Advanced (half day): Design the session handling for a checkout service so it survives a server restart and scales to twenty application instances. Compare server-side sessions in Redis with signed tokens against four criteria: per-request cost, ability to log a user out instantly, impact if the session store goes down, and complexity. Choose one, justify it, and describe what would make you change your mind.
Related reading
Estimation - the latency numbers used here, and how to turn them into load.
API Design - the next lesson: designing the HTTP contract itself.
Load Balancing - a close look at step 5 and how traffic is spread.
Security - tokens, TLS and authentication in more depth.
Enroll to keep reading
Lesson 4 of 12 · System Design Foundations: From Zero to Your First Design
Follow one web request from the URL bar to the response: DNS, TCP, TLS, HTTP versions, CDN, load balancer, app and database, plus where the time goes and how sessions work.
In the rest of this lesson
Step 4: HTTP, asking for the page
Step 5: edge, load balancer, application
Where the time goes
Stateless versus stateful servers
In practice: the checkout page is slow in one region only
Architect's notes
Enrolling is free. It unlocks every lesson in System Design Foundations: From Zero to Your First Design, tracks your progress across devices and keeps your quiz scores. The newsletter is optional.
Real architecture decisions, LLMOps patterns that survive production, and engineering leadership advice — from 12+ years of building at enterprise scale. Free. No spam. Unsubscribe anytime.
Get new articles by email
Free. We email a confirmation link first, and you can unsubscribe from any email.