Measuring request timing with curl shows whether delay comes from name resolution, connection setup, TLS negotiation, or the server's first-byte response. That split is useful when an API call feels slow, a health check starts missing latency targets, or two environments behave differently even though they return the same content.
curl exposes transfer timing fields through --write-out, printing values such as {time_namelookup}, {time_connect}, {time_appconnect}, {time_starttransfer}, and {time_total} after the request completes. Those values all start at the beginning of the transfer, so they show how much time is spent in DNS resolution, TCP setup, TLS handshake, server processing until the first byte, and the full transfer from start to finish.
These measurements come from the client side, so DNS cache state, proxies, redirect behavior, connection quality, and temporary congestion can shift the numbers between runs. When timing redirecting URLs, add --location so the final destination is measured consistently, and compare repeated samples instead of reacting to a single isolated result. The sample endpoints below stay masked while preserving the shape of a normal API health check and a slower first-byte response.
Steps to measure request timing with cURL:
- Run one request with the response body discarded and print only the total duration in seconds.
$ curl --silent --show-error --output /dev/null --write-out "time_total=%{time_total}\n" https://api.example.net/health time_total=0.184672time_total is the full elapsed time observed by curl from the start of the request until the transfer completes.
- Print the main timing phases for an HTTPS request to see where delay is accumulating.
$ curl --silent --show-error --output /dev/null --write-out "dns=%{time_namelookup}\nconnect=%{time_connect}\ntls=%{time_appconnect}\nttfb=%{time_starttransfer}\ntotal=%{time_total}\n" https://api.example.net/health dns=0.004318 connect=0.029441 tls=0.081972 ttfb=0.176548 total=0.176904time_starttransfer includes everything up to the first response byte, while time_total adds the remaining download time. For HTTPS requests, time_appconnect shows the TLS handshake portion of the transfer.
- Repeat the same request several times before drawing conclusions from a latency spike.
$ for i in $(seq 1 3); do curl --silent --show-error --output /dev/null --write-out "%{time_total}\n" https://api.example.net/health; done 0.181227 0.193904 0.172611Small variation between runs is normal because network path, remote load, and local cache state change continuously. Look for a stable range or repeated outliers instead of one best-case number.
- Compare a normal endpoint and a deliberately delayed endpoint with the same timing format.
$ for url in https://api.example.net/health https://api.example.net/health?profile=slow-ttfb; do curl --silent --show-error --output /dev/null --write-out "url=%{url_effective} ttfb=%{time_starttransfer} total=%{time_total}\n" "$url"; done url=https://api.example.net/health ttfb=0.178402 total=0.178691 url=https://api.example.net/health?profile=slow-ttfb ttfb=1.427561 total=1.427856A larger jump in time_starttransfer usually points to server-side delay before the first byte, while a wider gap between time_starttransfer and time_total points to response-body transfer time.
Mohd Shakir Zakaria is a cloud architect with deep roots in software development and open-source advocacy. Certified in AWS, Red Hat, VMware, ITIL, and Linux, he specializes in designing and managing robust cloud and on-premises infrastructures.
