← All posts

TTFB and the Server-Response Chain: Where Slow Starts

VisibilityIQ

Your TTFB reads 650ms and the instinct is to scale up the database, add a read replica, or buy a faster application tier. You profile the backend and the query that renders the page runs in 40ms. The other 610ms is somewhere you didn’t look — and it’s almost always one of two things you can’t fix by making the server faster.

TTFB is misdiagnosed more consistently than any other performance metric because the name implies a single cause. “Time to First Byte” sounds like server speed. It is actually the sum of two unrelated delays — the time your packets spend traveling the network, and the time your server spends thinking — and the fix for one does nothing for the other. Teams routinely spend a quarter on backend optimization when their problem was network distance, or stand up a global CDN when their problem was an N+1 query. The first move is never to optimize. It’s to attribute.

What TTFB Actually Measures

TTFB is the elapsed time from the moment the browser sends the request until the first byte of the response body arrives. Inside that window are at least five sequential phases, and they fall cleanly into two buckets.

The network bucket: DNS resolution, TCP connection establishment, and the TLS handshake. For a fresh connection to a distant origin, this is dominated by round-trip time. A single round trip between continents is 100–200ms. A traditional TCP-plus-TLS-1.2 setup needs roughly three round trips before the request itself is even sent — one for TCP’s SYN/SYN-ACK, two more for the TLS handshake. At 150ms per round trip, that’s 450ms consumed before your server has seen the request. This is pure geography and protocol overhead. No amount of backend optimization touches it.

The server bucket: request processing and response generation. Your application receives the request, runs middleware, queries the database, renders the template, and flushes the first byte. This is where backend work lives — and it’s the only part a faster server, a better query, or a warmer cache improves.

The single most useful instrument here is the Server-Timing response header. Emit it from your application with the backend processing duration, and you can read in DevTools — or in field RUM data — exactly how much of TTFB was your code versus the network. Without it, you are guessing at the split, and guessing is how teams end up optimizing the wrong half.

The Backend Half: Where Compute Time Hides

When the server bucket dominates, the usual suspects are predictable and individually measurable.

Cold starts. Serverless and edge functions that haven’t run recently incur an initialization penalty — loading the runtime, the dependency tree, and any global setup before the handler executes. On a cold path this can add 200–800ms to TTFB, and it’s invisible in your warm-path testing because your own repeated requests keep the function warm. Real users hitting an under-trafficked route pay it. The fix is provisioned concurrency, keep-warm pings, or moving to a runtime with negligible cold-start cost. Cloudflare Workers, for instance, use V8 isolates rather than containers and effectively eliminate the penalty — which is precisely why edge-rendered architectures sidestep this entire class of problem.

Unbounded or serial database work. The N+1 query pattern — one query to fetch a list, then one query per row — turns a single logical page into dozens of round trips to the database. Each round trip is small, but serialized they accumulate. A page that “should” render in 30ms balloons to 400ms because it issued 50 sequential queries. Batch them, join them, or cache the result. The tell is a backend time that scales with the amount of content on the page rather than staying flat.

Synchronous render of dynamic content. Server-side rendering that recomputes the full page on every request — re-running data fetches, re-rendering the component tree, re-serializing JSON — pays the full cost per visitor. If the content changes hourly but you render it per-request, you are doing thousands of times the necessary work. The answer is caching, which moves us to the network half of the story.

The Network Half: Distance Is the Enemy

When the network bucket dominates, the cause is almost always physical distance between the user and wherever the response is generated. The speed of light through fiber is a hard floor — roughly 1ms per 100km one way, before switching and routing overhead. A user in Tokyo and an origin in Frankfurt are separated by enough cable that no software change closes the gap. You move the response closer instead.

Edge caching is the highest-leverage TTFB fix that exists. When a CDN or edge node serves a cached HTML response, the request never reaches your origin. TTFB collapses to the round trip between the user and the nearest edge POP — typically 10–50ms — plus the trivial time to read the cached bytes. The 610ms of mystery latency from the opening example is most often a non-cached HTML document making a full transcontinental trip to an origin that then renders it from scratch. Cache that document at the edge and TTFB drops by an order of magnitude for every user not near your origin.

The objection is always the same: “my pages are dynamic, I can’t cache the HTML.” Usually you can, and the mechanism is stale-while-revalidate.

Stale-While-Revalidate: Caching the Uncacheable

The Cache-Control: stale-while-revalidate directive is the bridge between “fast but stale” and “fresh but slow.” It tells the cache: serve the cached copy immediately even if it’s expired, then refresh it in the background for the next visitor.

Consider Cache-Control: max-age=60, stale-while-revalidate=600. For the first 60 seconds, the cached response is fresh and served instantly. Between 60 and 660 seconds, the response is stale — but instead of blocking the user while it fetches a fresh copy, the edge serves the stale copy at full speed and asynchronously revalidates against the origin. The user gets a 30ms TTFB on slightly-stale content; the next user gets the refreshed version. Only requests arriving after the full 660-second window pay the origin round trip.

This is the pattern that lets genuinely dynamic sites have edge-fast TTFB. The content is at most a few minutes behind, which for the vast majority of pages — articles, product listings, category pages, marketing pages — is indistinguishable from real-time. The exceptions are pages that are truly per-user (a logged-in dashboard, a cart) or where second-level freshness is contractual (live pricing, stock levels). Everything else is a candidate. Tune max-age to your tolerance for staleness and stale-while-revalidate to the window during which you’d rather serve stale-but-instant than fresh-but-slow.

Why TTFB Gates Every Other Metric

TTFB is not a Core Web Vital, and yet fixing it can move all three. The reason is sequential: nothing the browser does happens before the first byte arrives.

LCP’s own attribution model lists TTFB as its first sub-part. The Largest Contentful Paint clock starts at navigation and can’t stop until the largest element paints — and that element can’t paint until the HTML arrives, gets parsed, and the resource is discovered and fetched. Every millisecond of TTFB is a millisecond added directly to the floor of LCP. A 600ms TTFB means your best-possible LCP is 600ms plus everything downstream. You cannot have a good LCP on top of a bad TTFB; the arithmetic forbids it.

The dependency extends further. The browser can’t begin discovering subresources — CSS, fonts, the LCP image — until it has parsed enough of the HTML, which it can’t do until the bytes arrive. A slow TTFB delays the entire critical request chain, pushing out resource load delay and, transitively, first paint and interactivity. This is why TTFB belongs first in any performance investigation: it’s the one number that sits upstream of everything else. Optimize a 400KB JavaScript bundle all you want; if TTFB is 900ms, the user still stares at a blank screen for nearly a second before your optimization can matter.

The practical rule: if TTFB at p75 is above 800ms, fix it before touching anything else, because it’s almost certainly your dominant LCP contributor. If TTFB is already under 200ms, stop optimizing it — it’s not your problem, and the gains are downstream in resource discovery and render-blocking.

HTTP/3 and the Handshake Tax

The protocol layer contributes to the network half, and HTTP/3 is the current best answer. Running over QUIC instead of TCP, it folds the transport and cryptographic handshakes into a single round trip rather than the two-to-three of TCP plus TLS 1.2 — and on a resumed connection, QUIC’s 0-RTT can send the request in the very first packet. It also eliminates transport-layer head-of-line blocking: under TCP, a single lost packet stalls every stream behind it; under QUIC, only the affected stream waits.

The honest framing: HTTP/3 helps most where the network is hostile. On a lossy mobile link or a high-latency international route, the handshake savings and the absence of head-of-line blocking can take 50–150ms off TTFB. On a clean connection to a nearby edge node, the difference is often inside the measurement error. Enable it — modern CDNs make it a configuration toggle and there’s no downside — but understand that it’s a refinement of the network half, not a substitute for caching the response closer to the user.

The Attribution Sequence

The whole discipline reduces to one ordered procedure. First, emit Server-Timing and read the backend duration; subtract it from total TTFB to get the network portion. Second, decide which half dominates. If backend dominates, hunt for cold starts, N+1 queries, and per-request rendering — and cache the result so the work happens once, not per visitor. If network dominates, the response is being generated too far from the user; move it to the edge with stale-while-revalidate and enable HTTP/3. Third, re-measure at p75 in the field, because a backend fix verified on your warm local connection tells you nothing about the cold-start tail or the geographically distant user.

The recurring mistake is skipping the attribution step and optimizing on instinct. A 650ms TTFB that is 40ms backend and 610ms network does not get better with a faster database, and a 650ms TTFB that is 600ms backend does not get better with a CDN. The measurement comes first; the fix is whichever half the measurement indicts.

VisibilityIQ reconciles field TTFB from CrUX against lab measurements and surfaces the backend-versus-network split where Server-Timing data is available, flagging pages where TTFB is the dominant LCP sub-part rather than treating it as an isolated number. The platform’s render and performance checks tie a slow first byte back to the specific cause — origin distance, missing edge cache directives, or per-request rendering — so the fix targets the half that’s actually slow instead of the half that’s easiest to imagine.

Frequently asked questions

Is a high TTFB always a server problem?
No. TTFB is the sum of network round-trip time and server processing time, and they have completely different fixes. A user in Sydney hitting an origin in Virginia pays roughly 160ms in pure round-trip latency before your server runs a single line of code — that's geography, solved with edge caching or a CDN, not faster application code. Conversely, a 900ms TTFB from a user 20ms away is almost entirely backend: database queries, template rendering, or a cold serverless start. Always split the measurement before choosing a fix. The Server-Timing header is the cleanest way to expose backend time so you can subtract it from total TTFB.
What is a good TTFB target?
Google's guidance is under 800ms at the 75th percentile of real users, but that's a ceiling, not a goal. TTFB is not a Core Web Vital itself — it's a diagnostic that gates LCP, which is. A site serving cached HTML from an edge node routinely hits 50–200ms TTFB; a dynamic page rendering server-side on every request often sits at 400–700ms. Treat 800ms as the line below which you stop worrying about TTFB and start worrying about resource load delay and render-blocking. Above 800ms, TTFB is likely your single largest LCP contributor and deserves attention first.
Does HTTP/3 improve TTFB?
Modestly, and mostly on lossy or high-latency connections. HTTP/3 runs over QUIC instead of TCP, which eliminates head-of-line blocking at the transport layer and combines the transport and TLS handshakes into a single round trip — versus the two or three round trips of TCP plus TLS 1.2. On a mobile connection with packet loss, that handshake saving can shave 50–150ms off the time before the first byte arrives. On a clean fiber connection to a nearby edge node, the difference is often within measurement noise. HTTP/3 is worth enabling, but it optimizes the network half of TTFB, not the backend half.