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
- curl 7.52 or later. The
%{time_pretransfer}and%{time_starttransfer}variables are documented in the curl manual under--write-out. awk, for the subtraction. Git Bash and every Linux shell ship it.- A URL that answers 200. A redirect makes curl report the timings of the last hop only.
Steps
- 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.165309Every 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.
- 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 msThis is a second run, so the totals differ from step 1 by tens of milliseconds. The server wait held near 33 ms in both.
- 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/CORSstarttransfer 0.202904 total 0.217815 size_download 30865The 30865 bytes of body cost 14.9 ms, and none of it counts toward TTFB.
- 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
Thresholds
What to check next
- How to check DNS lookup time: the first leg inside
pretransfer, and the one that varies most between runs. - How to check page size: explains the distance between
starttransferandtotal. - How to check if CDN cache is hit or miss: a miss adds the origin round trip to the server wait.
- How to check if keep-alive is enabled: a reused connection removes TCP and TLS from
pretransferentirely.
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.
Related on this site
basic4 minpublished updated Maks Verny