How to check TTFB

Run curl -s -o /dev/null -w '%{time_starttransfer}' https://example.com/ and subtract %{time_pretransfer} from it. The difference is the wait for the first response byte after the connection is ready. On example.com this run gave 122.0 ms and 88.7 ms, so the server took 33.3 ms.

Why check this

TTFB is the number a tester quotes when a page feels slow and nobody agrees on whose problem it is. Measure it on staging before a release and again after any infrastructure change: a new database index, a cache layer, a move to another region. Without it, a 900 ms server handler ships behind a fast front end and the bug lands as "the site is slow", which nobody can act on.

The one number tells you where to look next. A large gap between connection ready and first byte points at server code. A small gap with a slow total points at the connection or the download, and those are different tickets.

Prerequisites

Steps

  1. Step 1.

    Print the whole timing breakdown for one request.

    curl -s -o /dev/null -w 'dns           %{time_namelookup}\nconnect       %{time_connect}\ntls           %{time_appconnect}\npretransfer   %{time_pretransfer}\nstarttransfer %{time_starttransfer}\ntotal         %{time_total}\n' https://example.com/
    
    dns           0.008115
    connect       0.039609
    tls           0.131537
    pretransfer   0.131843
    starttransfer 0.165146
    total         0.165309

    Every value is seconds since the request started, so they accumulate. TLS finished at 131.5 ms and the first byte arrived at 165.1 ms.

  2. Step 2.

    Subtract the two milestones so the server wait stands alone.

    curl -s -o /dev/null -w '%{time_pretransfer} %{time_starttransfer}\n' https://example.com/ | awk '{printf "connection ready at %.1f ms, first byte at %.1f ms, server wait %.1f ms\n", $1*1000, $2*1000, ($2-$1)*1000}'
    
    connection ready at 88.7 ms, first byte at 122.0 ms, server wait 33.3 ms

    This is a second run, so the totals differ from step 1 by tens of milliseconds. The server wait held near 33 ms in both.

  3. Step 3.

    Confirm that TTFB stops at the first byte, by comparing it with the total on a document large enough to take time to download.

    curl -s -o /dev/null --compressed -w 'starttransfer %{time_starttransfer}\ntotal         %{time_total}\nsize_download %{size_download}\n' https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS
    
    starttransfer 0.202904
    total         0.217815
    size_download 30865

    The 30865 bytes of body cost 14.9 ms, and none of it counts toward TTFB.

  4. Step 4.

    Confirm that TTFB does carry server thinking time, using an endpoint that sleeps for one second.

    curl -s -o /dev/null -w '%{time_pretransfer} %{time_starttransfer}\n' https://httpbin.org/delay/1 | awk '{printf "connection ready at %.1f ms, first byte at %.1f ms, server wait %.1f ms\n", $1*1000, $2*1000, ($2-$1)*1000}'
    
    connection ready at 482.9 ms, first byte at 1617.3 ms, server wait 1134.4 ms

How to read the result

| What you see | What it means | What to do | | --- | --- | --- | | Server wait under 50 ms | The handler answers at once | Nothing on the server. If the page still feels slow, the cost is in setup or download. | | Server wait in the hundreds of ms | Application code or a query is the cost | Profile the handler. Cache headers will not help a first, uncached view. | | pretransfer already large, small wait after it | DNS, TCP or TLS dominates | Read How to check DNS lookup time and the connection reuse check below. | | total far above starttransfer | The body download dominates | Size and compression, not the server. See How to check page size. |

Common mistakes

Sign: TTFB looks excellent on the tester's machine and terrible in the field report.Cause: A single curl run measures one network path from one location with a warm resolver cache. The field number is a 75th percentile across real connections. Quote both, and never replace the field number with the local one.
Sign: time_starttransfer is quoted as TTFB without subtracting anything.Cause: That value includes DNS, TCP and TLS. On example.com it was 165.1 ms while the server itself took 33.3 ms. Reporting the whole number sends an infrastructure problem to the application team.
Sign: The measured TTFB drops by half on the second run and the fix is credited for it.Cause: The resolver cache and the server cache are both warm by then. Run the cold measurement first, and record which run produced the number you report.

Thresholds

TTFB of 0.8 s or less is good, above 1.8 s is poor, measured at the 75th percentile of real users Source: https://web.dev/articles/ttfb
example.com answered in 33.3 ms of server wait on this machine on 2026-09-11, one sample, not a benchmark Source: https://curl.se/docs/manpage.html#-w

What to check next

FAQ

How to measure TTFB with curl?

Use -w '%{time_starttransfer}' with -o /dev/null so the body is discarded, then subtract %{time_pretransfer}. The pair is printed by one request, so the two values describe the same connection.

How to check TTFB in Chrome?

Open DevTools, Network tab, click the document request, then the Timing panel. The row named "Waiting for server response" is the same measurement as the subtraction above. Tick "Disable cache" first, or the request never reaches the server.

Does TTFB include the page rendering?

No. It ends at the first response byte. Everything the browser does afterwards, parsing, stylesheets, layout, belongs to other metrics.

Why is my TTFB different on every run?

Route, resolver cache, server cache and CDN node all change between runs. Take several measurements, report the slowest cold one, and say how many runs you took.

Verified

Verified by Maks Vernycurl 8.21.0

Each output block is what the command above it printed on that date, on the host named in the step. Figures read from a live site move between runs. Compare the shape of the answer rather than the digits, and see the methodology for how a page is re-verified.

basic4 minpublished updated Maks Verny