How to reduce TTFB (time to first byte): causes, targets and fixes that work

Request timeline with DNS, TCP and TLS segments followed by a long server-wait bar that shrinks to a short one after HTML is cached at a CDN edge

To reduce TTFB (time to first byte), make your server answer the HTML request faster and make that answer travel a shorter distance. In practice that means caching rendered HTML (at a CDN edge or in nginx), speeding up the application and database behind uncached pages, and removing redirects and extra round trips. A well-tuned site typically returns its first byte in under 200 ms near the origin and under 100 ms anywhere when HTML is served from an edge cache.

What TTFB measures and why tools report different numbers

TTFB is the time between the browser asking for a page and the first byte of the response arriving. The trouble is that tools disagree about when the clock starts.

  • Full navigation TTFB starts when the navigation begins. It includes redirects, DNS lookup, the TCP connection, the TLS handshake and then the server's response. This is what Chrome's field data (CrUX) and the TTFB numbers in PageSpeed Insights use, as defined on web.dev.
  • Server wait starts when the request has been sent on an open connection and ends at the first response byte. Chrome DevTools labels it "Waiting for server response". Global Website Speed Test reports this value as TTFB and shows DNS, TCP and TLS as separate segments, so you can tell a slow backend from a slow network.

Server wait is not pure processing time. The request has to travel to the server and the first byte has to travel back, so it always contains one full network round trip (RTT). Roughly:

full TTFB  ≈ redirects + DNS + TCP (1 RTT) + TLS (1 RTT on TLS 1.3, 2 on TLS 1.2) + server wait
server wait ≈ 1 RTT + time your stack needs to produce the first byte

Light in fiber covers about 200,000 km per second, which works out to roughly 1 ms of round-trip time per 100 km of fiber path. A server in Virginia is typically about 75 ms from London and about 180 ms from Seoul. If your backend takes 300 ms to render a page, Seoul sees a server wait near 480 ms before a single byte of HTML arrives, and that is before DNS, TCP and TLS add their own round trips.

What is a good TTFB? Targets for origin and far regions

Judge TTFB per region, not as one number. The region closest to your server shows what your stack can do. Far regions show what distance costs you.

Where you measureWhat it includesTarget
Origin region, server waitOne short RTT plus backend time200 ms or less (A+ limit); under 100 ms with cached HTML
Far region, uncached HTMLOne long RTT (typically 70–300 ms) plus backend timeRTT plus 200 ms or less; worst region at most 1.5 s
Far region, HTML cached at the edgeShort RTT to a nearby edge plus cache lookupTypically under 100 ms
Average of all 8 regions (our grade)Mean server wait800 ms or less for A+, 1.2 s or less for A
Field TTFB, 75th percentile (CrUX)Redirects, DNS, TCP, TLS and server wait800 ms or less is good; over 1.8 s is poor

TTFB is not one of the Core Web Vitals, but it is the first part of Largest Contentful Paint. With an LCP budget of 2.5 seconds, a 1.2-second TTFB leaves little room for CSS, fonts and the hero image.

What causes high TTFB

Almost every slow first byte comes down to one of six causes. Your regional numbers usually point to the right one.

  1. Slow application code or database. Unindexed queries, N+1 query loops, heavy ORM hydration and blocking calls to third-party APIs during rendering. Symptom: high server wait even in the origin region.
  2. No HTML caching. Every visit rebuilds the same page from scratch. Typical of WordPress without a page cache, or a framework app that renders each request even though the content changes hourly.
  3. Cold starts. Serverless functions, containers scaled to zero, PHP-FPM in ondemand mode and sleeping shared hosts all pay a startup cost on the first request. Symptom: TTFB that swings between runs.
  4. Redirects. http:// to https:// to www to a trailing slash can add three full requests, each with its own round trip and sometimes its own DNS and TLS work.
  5. A distant origin. One server serving the whole planet. Symptom: fine near the origin, slow on other continents, with server wait growing in step with distance.
  6. Overloaded hosting. Too few PHP-FPM workers, noisy neighbors or a saturated CPU make requests queue before they are even processed. Symptom: TTFB that gets worse under traffic.

Cache full HTML pages at the edge and on the server

The fastest page is one your application never has to render. For anonymous visitors most pages (home, articles, product and category pages) are identical for everyone for minutes at a time, so cache the finished HTML and serve it in a few milliseconds.

nginx fastcgi_cache for PHP sites

For WordPress, Laravel or any PHP-FPM stack, nginx can cache rendered pages on disk in front of PHP. This configuration caches successful responses for 10 minutes, skips logged-in users and carts, and serves a stale copy while one background request refreshes it:

# http {} block
fastcgi_cache_path /var/cache/nginx/html levels=1:2 keys_zone=HTML:100m
                   max_size=2g inactive=60m use_temp_path=off;

# server {} block
set $skip_cache 0;
if ($request_method = POST) { set $skip_cache 1; }
if ($query_string != "")    { set $skip_cache 1; }
if ($http_cookie ~* "wordpress_logged_in|woocommerce_items_in_cart|remember_web") { set $skip_cache 1; }

location ~ \.php$ {
    include fastcgi_params;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    fastcgi_pass unix:/run/php/php8.3-fpm.sock;

    fastcgi_cache HTML;
    fastcgi_cache_key "$scheme$request_method$host$request_uri";
    fastcgi_cache_valid 200 301 10m;
    fastcgi_cache_use_stale error timeout updating http_500 http_503;
    fastcgi_cache_background_update on;
    fastcgi_cache_lock on;
    fastcgi_cache_bypass $skip_cache;
    fastcgi_no_cache $skip_cache;
    add_header X-Cache-Status $upstream_cache_status;
}

If your app sits behind nginx as a reverse proxy (Node, Python, Ruby), the same pattern works with proxy_cache_path, proxy_cache and the matching proxy_cache_* directives. Check the X-Cache-Status header: HIT means nginx answered without touching your app.

nginx will not cache a response that carries Set-Cookie or Cache-Control: private, no-cache or no-store. PHP adds these as soon as a page calls session_start(). Stop starting sessions on anonymous pages instead of telling nginx to ignore those headers, or one visitor's session can end up cached for everyone.

Cache-Control headers for HTML

To let a CDN cache HTML while browsers always check for a fresh copy, separate the browser lifetime from the shared-cache lifetime:

Cache-Control: public, max-age=0, s-maxage=600, stale-while-revalidate=60, stale-if-error=86400

max-age=0 makes browsers revalidate on every visit, s-maxage=600 lets shared caches such as a CDN keep the page for 10 minutes, and stale-if-error keeps the site up if the origin fails. Purge the CDN when you publish so the 10-minute window never shows outdated content. The MDN Cache-Control reference lists every directive, and our guide to how a CDN caches HTML at the edge covers cache keys and purging.

Use stale-while-revalidate so nobody waits for a rebuild

Without it, the first visitor after a cache entry expires waits for a full render, so TTFB spikes every few minutes. stale-while-revalidate=60 (or nginx's use_stale updating plus background_update) serves the old copy instantly and refreshes it in the background. CDN support for this directive varies, so confirm it in your provider's documentation.

Make the application and database respond faster

Pages that cannot be cached (carts, dashboards, search results) and every cache miss still depend on raw backend speed. Work through these in order.

Object caching with Redis

Store expensive query results and computed fragments in Redis instead of rebuilding them per request. In WordPress, a persistent object cache plugin backed by Redis removes most repeated option and meta queries. In Laravel, wrap heavy reads in Cache::remember('home:featured', 600, ...) with the Redis driver rather than the database or file driver.

Database indexes and N+1 queries

Turn on the slow query log, then run EXPLAIN on the worst offenders. A full table scan on a filtered or sorted column usually needs a composite index that matches the WHERE and ORDER BY. A page that runs hundreds of queries almost always has an N+1 loop, one query per item in a list. Fix it with eager loading (with() in Laravel, select_related in Django, includes in Rails).

# my.cnf: log every query slower than 100 ms
slow_query_log = 1
long_query_time = 0.1

PHP OPcache and runtime tuning

Run a current PHP version with OPcache sized so scripts never get evicted, and stop checking file timestamps in production:

opcache.enable=1
opcache.memory_consumption=256
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=20000
opcache.validate_timestamps=0   ; reload PHP-FPM on every deploy

Size PHP-FPM so requests never queue: set pm.max_children from available RAM divided by the memory each worker uses, and use pm = static or dynamic instead of ondemand on busy sites. The same principle applies to Node, Python and Ruby: enough workers, plus pooled database connections.

Cold starts on serverless and sleeping hosts

Keep at least one instance warm (provisioned concurrency or minimum instances), avoid hosting plans that suspend idle sites, and put an edge cache in front so cold starts only ever hit cache misses.

Remove network round trips: redirects, keep-alive, distance and early hints

Once the backend is fast, the remaining TTFB is mostly distance and round trips.

  • Avoid redirect chains. Redirect straight to the final URL in one hop, link to the canonical URL everywhere, and send HSTS so returning browsers skip the http:// redirect entirely. Check the chain with curl -sIL http://example.com | grep -i -E "^(HTTP|location)".
  • Keep connections alive. Browsers reuse connections by default, but the hops behind your server often do not. Enable upstream keep-alive between nginx and your app (keepalive 32; in the upstream block with proxy_http_version 1.1) and pool database connections.
  • Move the origin closer or go multi-region. Host near the majority of your visitors. If you have large audiences on several continents, add a second region with read replicas and latency-based DNS routing. Our guide on why websites are slow in other countries walks through the trade-offs.
  • Put a CDN in front. Even for uncached HTML, a CDN finishes DNS, TCP and TLS at a nearby edge and reuses warm connections to your origin. With HTML cached at the edge, the long trip disappears entirely.
  • Send 103 Early Hints. An interim 103 response (RFC 8297) lets the browser preload CSS and preconnect to other origins while your server is still generating the page. It does not shorten TTFB, but it puts the wait to work.
  • Stream the HTML. Flush the <head> as soon as the status code is known (streaming SSR in React or Next.js, or flush() in PHP with fastcgi_buffering off) so the browser starts fetching CSS before slow queries finish.
HTTP/1.1 103 Early Hints
Link: </assets/css/site.css>; rel=preload; as=style
Link: <https://cdn.example.com>; rel=preconnect

How to measure TTFB across 8 regions with Global Website Speed Test

A single-location test cannot separate backend time from distance. Run a free 8-region speed test and read the results in this order:

  1. Start with the origin region. The region tagged "origin" has the fastest TTFB and usually sits closest to your server. Its server wait is your backend speed plus a short round trip. Over 600 ms, the report flags a slow server: fix HTML caching and the application first. A+ requires 200 ms or less.
  2. Compare far regions with the origin. If a far region's TTFB is roughly the origin's value plus the round-trip time between them, the gap is pure distance, and only edge caching or a closer origin will close it. If every region is high by a similar amount, the backend is the bottleneck and your CDN is passing HTML straight through.
  3. Read the timeline bar. DNS, TCP and TLS appear as separate segments before the server-wait segment. Long TCP and TLS segments in far regions point to connection setup, which a CDN and HTTP/3 with TLS 1.3 shorten.
  4. Check first versus repeat visit. Repeat-visit load time should drop because static files come from the browser cache (see our browser caching guide). If repeat TTFB is far lower than first-visit TTFB, the first request probably warmed a page cache, a miss followed by a hit. Run the test twice to see steady-state numbers.
Change one thing at a time and re-test. A typical sequence: enable HTML caching, confirm X-Cache-Status: HIT or the CDN equivalent, re-test, then move on to queries and redirects. The methodology page explains exactly how each timing is captured.

Frequently asked questions

What is a good TTFB?

From a region near your server, aim for a server wait of 200 ms or less, and under 100 ms is realistic with cached HTML. For field data measured from the start of navigation, web.dev treats 800 ms or less as good and more than 1.8 seconds as poor.

Does TTFB affect SEO?

TTFB is not a Core Web Vital and not a direct ranking signal, but it is the first slice of Largest Contentful Paint. Every millisecond of TTFB delays LCP, which should be 2.5 seconds or less at the 75th percentile of real visitors.

Why do PageSpeed Insights, DevTools and other tools show different TTFB values?

They start the clock at different moments. Field TTFB in PageSpeed Insights is measured from the start of navigation, so it includes redirects, DNS, TCP and TLS. Chrome DevTools and Global Website Speed Test report only the wait from sending the request to receiving the first byte.

Why is my TTFB high only in some countries?

Distance. Even the server wait includes one full round trip, and light in fiber needs roughly 1 ms per 100 km of path in each direction. Caching HTML at CDN edge locations or adding an origin in that region removes most of the gap.

Will a CDN reduce TTFB on its own?

Only partly. A CDN caches static files by default but usually passes HTML through to your origin, so the server wait barely changes. You get the big TTFB drop only when the HTML itself is cached at the edge.

DevTeam Engineering builds and runs Global Website Speed Test, a free tool that measures page speed from 8 AWS regions at once. We publish guides based on what we see in thousands of multi-region tests. How we test.

Related guides

World map with one origin server in Virginia and CDN edge servers in London, Frankfurt, Singapore, Tokyo and Sydney answering nearby visitors from cache
CDN & Edge

What a CDN is and how it makes your website fast worldwide

A CDN serves files and cached HTML from edge servers near each visitor. Learn cache hits, keys, TTLs, purging and headers, and how a CDN cuts far-region TTFB.

9 min read · Updated Oct 4, 2026
A browser window pulling CSS, JavaScript, font and image files from a local cache drawer instead of a distant server, with a stopwatch showing a faster second visit
Caching

Cache-Control and browser caching: how to make repeat visits instant

Browser caching: set Cache-Control headers by file type, use immutable and stale-while-revalidate, bust caches with content hashes and speed up repeat visits.

9 min read · Updated Oct 4, 2026
World map with fiber-optic routes linking Seoul, Tokyo, Singapore, Sydney, Virginia, Oregon, Frankfurt and London, each route labeled with its round-trip time
Global Performance

Why your website is slow in other countries: latency, distance and fixes

Your website is slow in other countries because every round trip to a distant server adds latency, and a page load needs many. Learn the physics and the fixes.

9 min read · Updated Oct 4, 2026