In short. Time to First Byte is not one measurement, it is five stacked ones: DNS lookup, TCP connect, TLS handshake, request transfer and server processing. A single TTFB number tells you the total but never which stage consumed it. One curl command splits the total into its parts, and the split almost always points at one stage carrying most of the time.
What TTFB measures, and what it does not
TTFB is the interval between starting a navigation and receiving the first byte of the response body. It ends the moment the server starts answering - it says nothing about how long the page then takes to render, how large it is, or whether it is usable.
That boundary matters because TTFB is routinely blamed for problems it cannot cause. A page that renders slowly with a fast TTFB has a front-end problem, not a server one. Conversely, a slow TTFB delays everything downstream: no byte of HTML has arrived yet, so no stylesheet, script or image has even been discovered.
TTFB is not itself a Core Web Vital. It is a diagnostic metric that sits underneath Largest Contentful Paint - improving it moves LCP, but optimising it in isolation, without checking whether it is the dominant cost, is how teams spend a sprint on server tuning to gain very little.

The five stages inside a single TTFB number
| Stage | What happens | Typical owner | Fixed by |
|---|---|---|---|
| DNS lookup | Name resolved to an address | DNS provider, TTL policy | Faster authoritative DNS, sane TTLs |
| TCP connect | Handshake with the server | Network distance, routing | Edge presence closer to users |
| TLS handshake | Keys agreed, certificate validated | Protocol version, chain, resumption | TLS 1.3, session resumption, short chain |
| Request transfer | Request sent, server receives it | Uplink, request size | Rarely the problem |
| Server processing | Application builds the response | Code, database, upstream calls | Caching, indexes, async work |
Splitting the total with one command
curl exposes every boundary as a separate timer. These are cumulative from the start, so the stage durations are differences between neighbours.
curl -o /dev/null -s -w '
dns: %{time_namelookup}s
connect: %{time_connect}s
tls: %{time_appconnect}s
sent: %{time_pretransfer}s
ttfb: %{time_starttransfer}s
total: %{time_total}s
' https://example.com/
Read it as intervals, not as points:
# stage durations, derived from the cumulative timers above
DNS = time_namelookup
TCP connect = time_connect - time_namelookup
TLS handshake = time_appconnect - time_connect
server think = time_starttransfer - time_pretransfer
body download = time_total - time_starttransfer
The stage with the largest interval is where the investigation goes. Everything else is noise until that one is dealt with.
When DNS is the slow stage
A DNS interval that dominates points at the authoritative provider or at a resolution path that keeps missing cache. It is also the stage most often measured wrong: on a warm local resolver the lookup is near-instant, so your own repeat measurements will under-report what a first-time visitor experiences.
Measure it cold, and measure it from more than one resolver:
# Ask several resolvers and compare
for r in 1.1.1.1 8.8.8.8 9.9.9.9; do
printf '%s: ' "$r"
dig +noall +stats @$r example.com A | awk '/Query time/{print $4, $5}'
done
# What TTL are you publishing? Very low values force constant re-resolution.
dig +nocmd +noall +answer example.com A
Two structural causes are worth checking before tuning anything: a CNAME chain that requires several sequential lookups before an address is found, and a TTL set so low that virtually no visitor ever benefits from a cached answer. Verify both with the DNS lookup, and confirm that resolvers worldwide return the same record with the propagation check.
When connect and TLS dominate
Connect time is mostly physics: the distance between the visitor and the server, and the quality of the route between them. If connect is large and consistent, no server tuning will help - the request is spending its time on the wire.
TLS is more tractable. A large handshake interval usually comes from one of three things:
- An old protocol version. TLS 1.3 completes in one round trip where TLS 1.2 needs two, so the difference is a full round trip on every fresh connection.
- An unnecessarily long certificate chain. Every extra intermediate is more bytes in the handshake, and on a slow link that is measurable.
- No session resumption, so returning visitors repeat the full handshake instead of an abbreviated one.
# Which protocol is actually negotiated, and how big is the chain?
openssl s_client -connect example.com:443 -servername example.com < /dev/null 2>&1 \
| grep -E 'Protocol|Cipher|Verify return code'
# Count the certificates the server sends
openssl s_client -connect example.com:443 -servername example.com -showcerts < /dev/null 2>&1 \
| grep -c 'BEGIN CERTIFICATE'
The SSL checker reports the negotiated protocol and the served chain, which covers both of the first two causes without a terminal.

When server processing dominates, which is most of the time
If the gap between request sent and first byte is the largest interval, the time is being spent inside your application. The network did its job and the server is thinking.
Isolate what it is thinking about, in this order:
- Compare a cached and an uncached path. If a static asset returns quickly and a dynamic page does not, the cost is in generation, not in the stack underneath it.
- Look for slow database queries during the request, not in aggregate.
- Find synchronous external calls. Any request to a third-party API inside the response path adds that API's latency to your TTFB, and its outages to your outages.
- Check for lock or queue waits - time where the process is not computing but waiting for something else to release.
# Same page, cache bypassed, to separate cached from generated cost
curl -o /dev/null -s -w 'cold ttfb: %{time_starttransfer}s\n' "https://example.com/?cachebust=$(date +%s)"
curl -o /dev/null -s -w 'warm ttfb: %{time_starttransfer}s\n' "https://example.com/"
# Queries running longer than a second, right now
sudo mysql -e "SELECT id, time, state, LEFT(info,120) AS query
FROM information_schema.processlist
WHERE command <> 'Sleep' AND time >= 1
ORDER BY time DESC LIMIT 10;"
A synchronous third-party call inside the response path is the most commonly missed cause of erratic TTFB. It does not appear in your slow-query log, it does not consume your CPU, and it fails on someone else's schedule. If TTFB is unstable rather than uniformly high, look for one of these before anything else.
Why your measurement disagrees with the one in Search Console
Two very different kinds of data get called TTFB, and comparing them produces false conclusions in both directions.
| Lab measurement | Field measurement | |
|---|---|---|
| Source | Your curl, a speed test, a synthetic check | Real visitors' browsers |
| Network | One connection, usually good | Every network your audience uses |
| Cache state | Often warm after the first run | Mixed, mostly cold |
| Best used for | Isolating which stage is slow | Judging whether it is slow for users |
Field data aggregates real visitors, so it includes slow mobile connections, distant regions and cold caches that your own test never sees. A comfortable local number alongside poor field data is not a contradiction - it means your test conditions are not your audience's conditions.
Use them for different jobs. Field data answers whether there is a problem. Lab measurement answers where it is. Neither substitutes for the other, and the comparison of lab and field tools covers the distinction in more depth.
What counts as a good TTFB
Published guidance from Google's web performance documentation treats roughly 800 milliseconds as the boundary of "good" for TTFB, measured at the 75th percentile of real visits. Two cautions apply.
First, it is a percentile, not an average. A site can have a pleasant average and still fail, because the slowest quarter of visits is what the threshold measures. Second, TTFB is a means, not an end - the reason to improve it is that it delays LCP. If your LCP is already comfortable, further TTFB work buys less than it appears to.

Causes, ranked by how often they turn out to be the answer
| Dominant stage | Likely cause | Cheapest effective fix |
|---|---|---|
| Server processing | Uncached dynamic page generation | Full-page cache for anonymous visitors |
| Server processing | Unindexed or N+1 database queries | Index the query; batch the loop |
| Server processing | Synchronous third-party API call | Move it out of the response path |
| TCP connect | Server far from the audience | Edge caching or a closer region |
| TLS handshake | TLS 1.2 only, or no resumption | Enable TLS 1.3 and resumption |
| DNS | Slow authoritative DNS or CNAME chain | Flatten the chain; review TTLs |
| All stages, intermittently | Resource exhaustion under load | Find the concurrency ceiling first |
How to check your own site
Run the speed check for a page-level view including the timing breakdown, and the header checker to confirm whether responses are being served from a cache - an x-cache, age or cf-cache-status header changes the meaning of every number you just measured.
Single measurements are unreliable for a metric this sensitive to cache state and load. A page measured immediately after a deploy, with every cache cold, will look far worse than the same page an hour later. Scheduled monitoring records response time continuously, which is what turns TTFB from a number you took once into a trend you can attribute to a specific change.

Frequently asked questions
Is TTFB a ranking factor?
Not directly. Page experience signals are built on Core Web Vitals, and TTFB is not one of them. It matters because it delays Largest Contentful Paint, which is. Treat it as an input to a measured metric rather than as a target in itself.
Why is my TTFB fine in curl but poor in field data?
Because your test is one connection from one place, usually with a warm cache, and field data is every visitor on every network with mostly cold caches. Both numbers can be accurate. Use field data to decide whether there is a problem and lab measurement to find where it is.
Will a CDN fix a slow TTFB?
It fixes the stages that are about distance - connect and, for cached responses, server processing. It does nothing for a slow uncached page, because the request still travels to your origin and waits for the same work. If your split shows server processing dominating on uncached pages, a CDN moves the problem rather than solving it.
My TTFB is unstable rather than consistently high. What does that mean?
Instability usually means contention or an external dependency: a database lock, a queue that occasionally backs up, or a third-party call in the response path. A consistently high TTFB points at a fixed cost such as page generation; a spiky one points at something you are waiting on.
Does HTTP/3 improve TTFB?
It can, mainly by reducing connection setup cost and handling packet loss better on unreliable networks. The gain is concentrated in the connect and handshake stages, so it helps most when those are the dominant intervals in your split, and very little when server processing is.
Should I optimise TTFB before or after LCP?
Measure the split first. If TTFB is a small fraction of LCP, the remaining time is spent discovering, loading and rendering resources, and that is where the work belongs. If TTFB is the majority of LCP, it comes first because nothing downstream can start until it ends.
Checklist
- Split TTFB into stages before changing anything.
- Read intervals between timers, not the cumulative timers themselves.
- Measure DNS cold - a warm resolver hides the real cost.
- Confirm the negotiated TLS version and the number of certificates served.
- Compare a cache-busted request against a warm one to separate generation from delivery.
- Look for synchronous third-party calls when TTFB is erratic rather than uniformly high.
- Check for cache headers before interpreting any measurement.
- Judge against the 75th percentile of real visits, not an average.
- Confirm TTFB is actually the dominant part of LCP before optimising it.
- Track response time over time so a regression can be tied to a change.