Skip to content
RU
← All articles

Slow TTFB: How to Find the Stage That Is Actually Slow

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.

Horizontal timeline showing DNS lookup, TCP connect, TLS handshake, request send and server processing as consecutive segments that together form TTFB
TTFB is a sum of five consecutive intervals. Reporting only the total hides which one dominates.

The five stages inside a single TTFB number

StageWhat happensTypical ownerFixed by
DNS lookupName resolved to an addressDNS provider, TTL policyFaster authoritative DNS, sane TTLs
TCP connectHandshake with the serverNetwork distance, routingEdge presence closer to users
TLS handshakeKeys agreed, certificate validatedProtocol version, chain, resumptionTLS 1.3, session resumption, short chain
Request transferRequest sent, server receives itUplink, request sizeRarely the problem
Server processingApplication builds the responseCode, database, upstream callsCaching, 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.

Comparison of a TLS 1.2 handshake requiring two round trips against a TLS 1.3 handshake requiring one
Protocol version changes the number of round trips before any application data flows. On a high-latency link that difference is the whole handshake cost.

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:

  1. 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.
  2. Look for slow database queries during the request, not in aggregate.
  3. 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.
  4. 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 measurementField measurement
SourceYour curl, a speed test, a synthetic checkReal visitors' browsers
NetworkOne connection, usually goodEvery network your audience uses
Cache stateOften warm after the first runMixed, mostly cold
Best used forIsolating which stage is slowJudging 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.

Distribution chart showing a fast average alongside a slow 75th percentile tail
Thresholds are set at a percentile, not an average. A comfortable mean can hide a failing tail.

Causes, ranked by how often they turn out to be the answer

Dominant stageLikely causeCheapest effective fix
Server processingUncached dynamic page generationFull-page cache for anonymous visitors
Server processingUnindexed or N+1 database queriesIndex the query; batch the loop
Server processingSynchronous third-party API callMove it out of the response path
TCP connectServer far from the audienceEdge caching or a closer region
TLS handshakeTLS 1.2 only, or no resumptionEnable TLS 1.3 and resumption
DNSSlow authoritative DNS or CNAME chainFlatten the chain; review TTLs
All stages, intermittentlyResource exhaustion under loadFind 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.

Line chart of response time over several days with a clear step upward aligned to a deployment marker
A trend attributes a regression to a change. A single measurement can only tell you the number was high once.

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.

Check your website right now

Check your site's speed →
More articles: Performance
Performance
Gzip vs Brotli: Web Compression Compared
16.03.2026 · 585 views
Performance
CDN Cache Invalidation: Strategies for Fresh Content
16.03.2026 · 568 views
Performance
Resource Hints: Prefetch, Preload, Preconnect, DNS-Prefetch
16.03.2026 · 452 views
Performance
Latency vs Throughput: Network Performance Metrics
16.03.2026 · 408 views