Measure request timing

Measure how long each phase of a request takes (DNS, connect, TLS, first byte, total) to pinpoint where latency comes from.

Overview

Measure how long each phase of a request takes (DNS, connect, TLS, first byte, total) to pinpoint where latency comes from.

In code

curl -o /dev/null -s -w \
'dns:      %{time_namelookup}s\nconnect:  %{time_connect}s\ntls:      %{time_appconnect}s\nttfb:     %{time_starttransfer}s\ntotal:    %{time_total}s\n' \
  https://api.example.com/data
const t0 = performance.now();
const res = await fetch('https://api.example.com/data');
const ttfb = performance.now();
await res.arrayBuffer();
const done = performance.now();
console.log(`ttfb ${(ttfb - t0).toFixed(1)}ms, total ${(done - t0).toFixed(1)}ms`);
import requests

res = requests.get('https://api.example.com/data')
# elapsed = send -> last byte of headers received
print('elapsed:', res.elapsed.total_seconds(), 's')

Details

curl's `-w` (write-out) prints timing variables after the transfer. The key ones are `time_namelookup` (DNS), `time_connect` (TCP), `time_appconnect` (TLS handshake done), `time_starttransfer` (TTFB, first byte), and `time_total` (whole thing). Add `-o /dev/null -s` to discard the body and progress meter so you see only the numbers.

These variables are cumulative, so compute each phase by subtraction — e.g. pure TLS time = `time_appconnect - time_connect`. A high TTFB points to slow server processing, a high `namelookup` to DNS issues, and a high `connect` to network round-trip (RTT) latency.

In the browser use `performance.now()` or the Resource Timing API; in `requests` use `res.elapsed` (note it excludes body-download time). If the server sends a `Server-Timing` header you get a breakdown of its internal processing. For accurate benchmarks, repeat the measurement several times to reduce cache and keep-alive effects.

Related headers

Related status codes