Time to First Byte is the delay between a browser starting a request and receiving the first byte of the response. Everything else on the page waits behind it. This guide shows you how to split TTFB into its real components with a single curl command, work out whether the delay is network or server, and fix whichever part is responsible.
What TTFB actually includes
TTFB is not just "how slow the server is". Measured from the browser, it is the sum of several steps:
- Redirects, if any, each of which repeats the steps below.
- DNS lookup: resolving the hostname to an IP address.
- TCP connection: one round trip to the server (QUIC for HTTP/3 combines this with the next step).
- TLS handshake: one or two more round trips to negotiate encryption.
- Request travel and server think time: the server receives the request, runs your application, queries databases and starts sending the response.
- Response travel: the first byte crossing the network back.
Steps 2 to 4 and 6 depend on geography and connection reuse. Step 5 is the one you control most directly. A visitor far from the server pays several network round trips before any of your code runs, which is why a page can be quick for you and slow for someone else.
Measure it with curl
curl can report the timing of each phase. Save this format string to a file named curl-format.txt:
dns: %{time_namelookup}s
connect: %{time_connect}s
tls: %{time_appconnect}s
pretransfer: %{time_pretransfer}s
redirects: %{time_redirect}s
ttfb: %{time_starttransfer}s
total: %{time_total}s
http ver: %{http_version} status: %{http_code} ip: %{remote_ip}
Then run:
curl -o /dev/null -s -w "@curl-format.txt" https://example.com/
Or as a one-liner without a file:
curl -o /dev/null -s -w 'dns=%{time_namelookup} connect=%{time_connect} tls=%{time_appconnect} ttfb=%{time_starttransfer} total=%{time_total}\n' https://example.com/
All values are cumulative seconds from the start of the request, not durations of each phase. To read them:
- DNS time is
time_namelookup. - TCP connect is
time_connect - time_namelookup. - TLS is
time_appconnect - time_connect. - Server think time (plus one round trip) is
time_starttransfer - time_pretransfer. This is the figure to watch when tuning your application. - Add
-Lif the URL redirects, andtime_redirectwill show the time spent in redirects.
Run it several times, and compare a cold request with a repeated one. The first hit may include DNS and cache misses; later ones show steady-state behavior. Test both the home page and a deep dynamic page, and test with a cache-busting query string (?nocache=123) to see uncached performance. Where possible, measure from more than one location; the Website Speed Test and DNS Checker help you separate regional effects.
What counts as good
Treat these as guidance, not guarantees. Google's web performance guidance has described a TTFB of about 0.8 seconds or less as good and over about 1.8 seconds as poor, measured at the 75th percentile of real visits, and these figures include network time. In practical lab testing from a nearby location, a well-cached page often responds well under 200 ms of server time, while a dynamic page hitting a database may reasonably take a few hundred milliseconds. Persistent server think time above a second is usually a sign of something fixable: no cache, slow queries, or an overloaded host.
TTFB is not itself one of the Core Web Vitals, but it feeds directly into Largest Contentful Paint. See the Core Web Vitals guide for the wider picture.
If DNS is slow
DNS slowness is common on first visits. Causes include a slow authoritative nameserver, too many CNAME hops, and very low TTLs that force frequent lookups. Check your records with the DNS Checker, choose a reputable DNS provider with global anycast, keep the chain of CNAMEs short, and use sensible TTLs (minutes to hours for records that rarely change). The background is covered in DNS explained for website owners.
If connect and TLS are slow
These are mostly distance and handshake costs.
- Serve users from nearby. A CDN terminates connections near the visitor, shortening every round trip.
- Use TLS 1.3, which needs one round trip for a new connection instead of two for TLS 1.2, and supports session resumption.
- Enable keep-alive so browsers reuse connections instead of repeating DNS, TCP and TLS for every request. In nginx:
http {
keepalive_timeout 65;
keepalive_requests 1000;
}
- Use HTTP/2 or HTTP/3. HTTP/2 multiplexes requests over one connection; HTTP/3 runs over QUIC and handles packet loss and mobile network changes better, and can set up connections with fewer round trips. In nginx,
listen 443 ssl http2;enables HTTP/2 (newer versions use a separatehttp2 on;directive). Verify support with the HTTP/2 and HTTP/3 Checker. - Enable OCSP stapling where your CA still supports it, so clients do not make an additional lookup. Some CAs have stopped offering OCSP, so check before relying on it.
- Reduce redirect hops, since each redirect repeats the whole sequence. See HTTP redirects explained.
If server think time is the problem
This is the biggest lever for dynamic sites and where most of the achievable gains are.
Cache full pages
If most visitors see the same HTML, generate it once and serve it from cache. Options, from simplest:
- CMS page-cache plugins (for WordPress, a page cache plugin that writes static HTML) and framework-level caching.
- Reverse proxy or FastCGI cache in nginx. A minimal PHP-FPM example:
fastcgi_cache_path /var/cache/nginx levels=1:2 keys_zone=APP:50m inactive=60m max_size=1g;
server {
set $skip_cache 0;
if ($request_method = POST) { set $skip_cache 1; }
if ($query_string != "") { set $skip_cache 1; }
if ($http_cookie ~* "session|wordpress_logged_in") { set $skip_cache 1; }
location ~ \.php$ {
include fastcgi_params;
fastcgi_pass unix:/run/php/php-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_cache APP;
fastcgi_cache_valid 200 301 10m;
fastcgi_cache_bypass $skip_cache;
fastcgi_no_cache $skip_cache;
add_header X-Cache $upstream_cache_status always;
}
}
The X-Cache header lets you confirm hits and misses with curl -I. Never cache responses for logged-in users, carts or personalised pages, which is what the bypass rules are for.
- Object caching (Redis or Memcached) stores the results of repeated database queries or expensive computations when full-page caching is not possible.
Tune the database
Slow queries are a frequent culprit. Turn on MySQL or MariaDB's slow query log, find the worst offenders, and look at their plans:
# my.cnf
slow_query_log = 1
long_query_time = 0.5
slow_query_log_file = /var/log/mysql/slow.log
EXPLAIN SELECT * FROM orders WHERE customer_id = 42 ORDER BY created_at DESC LIMIT 20;
Typical fixes: add the missing index for the columns in WHERE and ORDER BY, stop selecting columns you do not use, remove N+1 query loops in application code, and clean up bloated tables such as expired transients or logs. Make sure the database server has enough memory to keep hot data in its buffer pool.
Enable PHP opcache and keep PHP current
Without opcache, PHP recompiles every file on every request. Enable it in php.ini:
opcache.enable=1
opcache.memory_consumption=192
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=20000
opcache.validate_timestamps=1
opcache.revalidate_freq=60
Raise max_accelerated_files and memory for large codebases; if opcache_get_status() shows the cache full, increase them. Newer supported PHP versions are generally faster than old ones. Also size PHP-FPM's pm.max_children to your memory; too few workers queue requests, and too many cause swapping.
Reduce work per request
Remove slow plugins or middleware, defer external API calls to background jobs, avoid synchronous calls to third-party services in the request path, and profile with the tools for your stack (Xdebug, Blackfire, or built-in profilers). Check CPU and memory on the host: an overloaded or throttled shared server hurts TTFB regardless of code quality. The Website Hosting Checker shows where and with whom your site runs, which can reveal a distant or overcrowded host.
Use a CDN and edge caching
A CDN improves TTFB in two ways. For cacheable content, it answers from a nearby edge with no origin contact. For uncacheable content, it still terminates TLS close to the user and reuses long-lived connections to your origin, cutting handshake costs.
- Set
Cache-Controlheaders deliberately:Cache-Control: public, max-age=300, s-maxage=3600lets browsers keep a page five minutes and shared caches an hour. - Cache static assets for a long time using versioned filenames:
Cache-Control: public, max-age=31536000, immutable. - Many CDNs can cache HTML if configured to bypass on cookies; test carefully to avoid serving one user's page to another.
- Confirm that the CDN is active, and what it is, with the CDN Detector. Look for headers like
cf-cache-status,x-cacheorage.
Geography
Physics sets a floor: light in fibre travels a few hundred kilometres per millisecond at best, and routing adds more. If your audience is concentrated in one region, host in or near it. If it is global, a CDN is the practical answer, with an origin placed near the largest share of uncached traffic.
A practical order of attack
- Run the curl command and write down each phase.
- Fix redirects and enable keep-alive and modern protocols (cheap and safe).
- Add a page cache or CDN for anonymous traffic.
- Profile the uncached path, fix slow queries, and enable opcache.
- Re-measure from several locations and keep a baseline so regressions are visible.
Next steps
- Measure from the web with the Website Speed Test
- Check protocol support in the HTTP/2 and HTTP/3 Checker
- Confirm CDN presence with the CDN Detector
- Review the Core Web Vitals guide
- Understand upstream failures in fix 504 Gateway Timeout
FAQ
Is TTFB the same as server response time?
No. TTFB measured in a browser includes redirects, DNS, connection and TLS time on top of server think time. Server response time is the part from request received to first byte sent, which you can approximate as time_starttransfer - time_pretransfer in curl.
What is a good TTFB?
Roughly under 0.8 seconds at the 75th percentile of real users is considered good in Google's guidance, and over about 1.8 seconds is poor. Aim lower on cached pages. Treat these as guidance, since the right target depends on your audience and network conditions.
Why is my TTFB high only on the first request?
Cold caches, a first DNS lookup, a fresh TLS handshake, or a stopped PHP worker pool waking up can all slow the first hit. If later requests are fast, focus on warming caches, keep-alive and connection reuse, and look at whether low traffic causes your host to sleep your application.
Will a CDN always lower TTFB?
Usually for static assets and cacheable pages. For uncacheable dynamic responses it helps with connection setup but still waits for your origin, so server-side fixes remain necessary.
Does HTTP/3 fix a slow server?
No. It improves connection setup and handles lossy networks better, but it cannot speed up application code or database queries.
How do I tell if the problem is my host?
If simple static files also respond slowly, CPU or memory is saturated, or the same code is fast locally, the host is the likely cause. Compare with a cached static page, and check resource graphs from your provider.
Get notified when this guide updates
Plus occasional practical tips on uptime and monitoring, nothing else. Unsubscribe anytime.
This page is general information written by our editorial team and updated when documentation changes. Servers and providers differ, so test changes safely first. See our editorial policy and disclaimer.