Archtin
All articles
NetworkingSystem DesignHTTP13 min read

The Request That Never Reached Your Server

You hit Enter, you get a 200 OK — but your backend may have done nothing. Follow one HTTP request through browser cache, DNS cache, connection reuse, CDN, WAF and load balancer to every layer where it can stop.

You hit Enter. Your server did nothing

You type https://example.com/products/123 and press Enter. The mental model most engineers carry is browser → backend. Not even close. Your request may pass through five or more layers of infrastructure before your application code runs — and here is the part that surprises people: it can stop at almost every one of them. If it does, your backend never learns you asked.

This is not a corner case. On a well-tuned system, the majority of requests never touch the origin. Understanding where a request can terminate — and what each layer actually decides — is the difference between debugging in minutes and debugging for hours.

The actual journey

BrowserBrowser cacheHIT → response 🛑DNS resolutioncached IP → no lookupConnectionreuse → skip handshakesCDNthe biggest plot twistCache HITresponse 🛑MISSWAF → Load balancerBackend (finally)
A request is a decision tree, not a straight line. Dashed branches are early exits where your backend is never involved.

The naive picture — You → Browser → OS → DNS → CDN → Load Balancer → Server — is a useful mental model, but it describes only the cold, cache-miss path. The accurate picture is conditional: every layer first asks “can I answer this myself?”

Stop 1: browser cache

You request example.com/logo.png. Before anything touches the network, the browser checks its own cache: do I already have a fresh copy of this? If the previous response came with Cache-Control: max-age=86400 and it is still fresh, the browser returns it from disk or memory.

  • 🚫 No DNS request
  • 🚫 No TCP or TLS setup
  • 🚫 No CDN, no load balancer, no backend

The network panel shows 200 OK (from disk cache) and zero milliseconds of network time. One important distinction: this applies to cacheable resources — static assets, and API responses explicitly marked cacheable — not blindly to every request. Anything sensitive should carry no-store, precisely so it cannot take this shortcut.

Stop 2: OS / DNS cache

Suppose the browser does need the network. First question: where is api.example.com? That name must become an IP address, and that lookup has its own cascade of caches: browser DNS cache → OS resolver cache → the recursive resolver (your ISP or 8.8.8.8) → root / TLD / authoritative servers.

Browser DNS cache      (seconds to minutes)
        ↓ miss
OS resolver cache      (stub resolver, honours TTL)
        ↓ miss
Recursive resolver     (ISP / 8.8.8.8 — the big cache)
        ↓ miss
Root → TLD → Authoritative  (the full lookup, rarely reached)
Each level of DNS caching short-circuits the ones below it.
The detail that prevents oversimplification
DNS never returns your API response. It answers exactly one question: “which IP should I connect to?” A cached DNS answer means the request continues faster — it does not mean the request was served early. The stop here is a stop of the lookup, not of the request.

Shortcut: connection reuse

Now the browser knows where to connect — but it may not need to connect at all. If an existing connection to that origin is still warm, it simply reuses it.

PathWhat happens before your bytes move
Cold connectionTCP handshake (1 RTT) → TLS handshake (1–2 RTT) → HTTP request
Warm HTTP/1.1 keep-aliveHTTP request (but only one at a time per connection)
Warm HTTP/2 connectionNew stream on the existing multiplexed connection — zero setup
HTTP/3 0-RTT resumptionRequest rides inside the very first QUIC packet

The mechanics differ by protocol version and connection state, but the idea is constant: the browser works very hard not to pay connection setup twice. On a warm connection, the 2–3 round trips of setup collapse to zero.

Stop 3: the CDN plot twist

The request finally reaches the network. Your backend still might not see it — because there is a CDN with points of presence around the world, terminating requests a few milliseconds from the user.

GET /images/product-123.jpg

                 ┌── Cache HIT  → serve from edge → user   (origin: silent)
Request → CDN ───┤
                 └── Cache MISS → fetch from origin, cache, serve
The CDN is a fork in the road.

On a hit, the whole exchange is User → CDN → cached response → User. Your backend's logs show nothing; its metrics show nothing; from its perspective the request never existed. This is exactly why CDNs cut origin traffic by 80–95% on asset-heavy sites — and why "the site is up but users see stale data" bugs are so confusing until you remember this layer exists.

WAF, load balancer, and beyond

On a cache miss the request continues inward, and there can be several more gates before application code:

1CDN (miss)2WAF3Load balancer4API gateway5Service6Database
  • WAF — inspects the request for SQLi, XSS and bot patterns. A blocked request dies here with a 403; your backend never sees the attack.
  • Load balancer — picks a healthy instance (round robin, least connections) and can return a 503 itself when nothing healthy exists.
  • API gateway — auth verification, rate limiting, routing. An expired token is rejected here with a 401 without touching your service.

Only after all of this does your application server process the request — possibly for the first time any of your code has run.

One request, many endings

LayerEarly exitWhat your backend sees
Browser cacheFresh cached copy → responseNothing
DNS cachesLookup short-circuited (request continues)Nothing (normal)
Connection reuseHandshake skipped (request continues)Nothing (normal)
CDN cache hitEdge serves the responseNothing
WAFMalicious request blocked (403)Nothing
Load balancerNo healthy targets (503)Nothing
API gatewayAuth/rate-limit rejection (401/429)Nothing
BackendFinally, your code runs
The uncomfortable implication
When you see 200 OK, it does not necessarily mean “my backend generated this response.” It means some layer answered. Knowing which layer answered is often the entire debugging problem.

The debugging takeaway

When latency or a weird response appears, do not open your backend logs first. Walk the chain from the outside in:

  1. Browser cache? — check (from disk cache) and the response's Age header.
  2. DNS? — slow time_namelookup in curl, or a stale IP after a failover.
  3. Connection? — high time_connect/time_appconnect means setup, not app, is slow.
  4. CDN?X-Cache: Hit/Miss, CF-Cache-Status, or the Via header tell you who served it.
  5. WAF / gateway? — a 403 or 429 with no application log line is the signature.
  6. Load balancer? — its own metrics show 5xx your app never emitted.
  7. Backend — only now do your logs become the source of truth.

Because sometimes the slow request never reached your application at all — and no amount of query optimisation will find it there.

Interview framing

“Between the browser and my service there are multiple layers where a request can terminate: browser cache, DNS caching, connection reuse, the CDN, a WAF, the load balancer and the API gateway. I design with that in mind — cacheable responses get explicit Cache-Control, personalized data gets no-store, and every layer adds an identifying header like X-Cache or a request ID so I can tell who answered. When debugging, I walk the chain from the edge inward instead of assuming my backend saw the request.”

Keep reading

Suggested next articles based on this one.

Design it, don't just read it.

Practise LLD and system design problems with structured rubrics and AI feedback.

Start practising free