Improving TTFB for Static HTML

Time to first byte is the part of page loading that static sites are supposed to have solved. There is no database query, no template rendering, no application server — just a file. Yet field data regularly shows static sites with TTFB at p75 of 600 ms, 800 ms or more on mobile, and since LCP cannot start before the HTML arrives, that time comes straight off the LCP budget. The cause is never generating the page; it is where the page comes from and how many round trips it takes to get there.

This guide breaks TTFB into its parts for a static site, measures each in the field, and fixes the ones that matter: HTML not cached at the edge, redirect chains, connection setup and origin placement, plus 103 Early Hints for what remains. It is part of Largest Contentful Paint Optimization for Static Sites. The site is a documentation site served from S3 in Frankfurt behind a CDN, with a global audience; its mobile TTFB at p75 was 640 ms before this work.

Prerequisites

  • Field TTFB with sub-parts from the Navigation Timing API (the web-vitals attribution build reports them).
  • CDN analytics or logs with cache status for HTML.
  • Access to CDN caching and redirect configuration.

Step 1: Break TTFB Into Its Parts

The web-vitals attribution build splits TTFB into waiting time (before the request could start, including redirects), DNS, connection (TCP and TLS), and request time (from sending the request to the first byte):

import { onTTFB } from 'web-vitals/attribution';
onTTFB(({ value, attribution: a }) => beacon({
  metric: 'TTFB', value, cacheStatus: performance.getEntriesByType('navigation')[0]?.serverTiming?.find((t) => t.name === 'cdn-cache')?.description,
  waiting: a.waitingDuration, dns: a.dnsDuration, connection: a.connectionDuration, request: a.requestDuration
}));

The CDN was configured to add a Server-Timing: cdn-cache;desc=HIT header, so each beacon records whether the HTML came from the edge cache. A week of mobile data at p75:

Partp75Main cause
Waiting (incl. redirects)190 mswww and trailing-slash redirects from shared links
DNS40 msfirst visit lookups
Connection (TCP + TLS)110 msTLS 1.3 without 0-RTT on some paths
Request (to first byte)280 ms41% of HTML requests missed the edge cache
Breakdown of mobile TTFB into waiting, DNS, connection and request time Horizontal bars of the 75th percentile of each TTFB part on mobile. Request time 280 milliseconds, mostly edge cache misses fetched from Frankfurt. Waiting time 190 milliseconds, mostly redirects. Connection time 110 milliseconds. DNS 40 milliseconds. Mobile TTFB at p75, by part Request (cache misses) 280 ms Waiting (redirects) 190 ms Connection (TCP + TLS) 110 ms DNS 40 ms
Percentiles of parts do not add up to the percentile of the total; they show where the time goes.

Step 2: Cache HTML at the Edge

The largest part was request time on cache misses: HTML was served with Cache-Control: max-age=0, must-revalidate, which CDNs interpret as "revalidate with the origin every time". Each revalidation is a round trip to Frankfurt — about 20 ms for European readers and 300 ms or more for Asia-Pacific. The fix is to let the edge cache HTML for a short time and serve stale while revalidating, while browsers still revalidate:

Cache-Control: public, max-age=0, s-maxage=300, stale-while-revalidate=86400

s-maxage=300 lets the CDN serve the HTML for five minutes without asking the origin; stale-while-revalidate lets it serve a stale copy while fetching a new one in the background; deploys purge HTML so new content appears immediately. The details and trade-offs are in Stale-While-Revalidate for Static HTML. Tiered caching, as in Tiered Caching and Origin Shield, then served most remaining misses from a regional tier. The HTML edge hit ratio went from 59% to 95%.

Step 3: Remove Redirect Hops

Waiting time was dominated by redirects. Analytics showed that 23% of landing navigations arrived through at least one redirect: www. to the apex domain, slash-less URLs to trailing-slash URLs, and old paths from a migration — and some links from the company's own marketing emails chained all three. Each hop is a full round trip before the real request can begin.

Three changes: marketing links and the site's own internal links were updated to final URLs (a link checker in CI now treats internal redirects as errors, as in Checking Links in Pull Requests); the redirect rules were flattened so every old URL goes to its final destination in one hop; and redirects moved from the origin to the edge, so the remaining hops take one short round trip to the nearest edge rather than one to Frankfurt, as described in Caching 404s and Redirects at the Edge.

Navigation timeline for a shared link with redirects A timeline for a mobile reader in Singapore following a shared link. Before, the www redirect completed at 240 milliseconds, the trailing slash redirect at 480 milliseconds, and the final HTML's first byte arrived at 810 milliseconds after an origin fetch. After flattening redirects to one edge hop and caching HTML at the edge, the single redirect completed at 60 milliseconds and the first byte of cached HTML arrived at 150 milliseconds. A shared link with two redirects, before and after www redirect (before) slash redirect (before) First byte (before) One edge redirect (after) First byte (after) 240 ms origin round trip 480 ms origin round trip 810 ms cache miss to Frankfurt 60 ms nearest edge 150 ms edge cache hit
Timings from Navigation Timing on a real device over 4G; the after case uses one edge redirect.

Step 4: Connection Setup

Connection time — TCP plus TLS — is paid once per origin per visit, but it is paid on the navigation request, so it lands directly in TTFB. Checks and changes:

  • TLS 1.3 and HTTP/3 enabled at the CDN. HTTP/3 over QUIC combines transport and TLS handshakes; on this site's mobile traffic, 58% of navigations used HTTP/3 after enabling it, and their connection time at p75 was 38 ms lower than HTTP/2.
  • Certificate chain size. A certificate chain with an extra intermediate added a round trip for some clients; switching to the CDN's managed certificate with an ECDSA key and a short chain removed it.
  • DNS. A low TTL of 60 seconds on the apex record, left over from a migration, caused frequent re-resolution; raising it to one hour cut DNS time at p75 from 40 ms to 22 ms.

Step 5: Use 103 Early Hints for What Remains

Even a fast TTFB is time the browser spends idle. 103 Early Hints lets the CDN send Link headers for critical resources before the final response, so the browser starts fetching the stylesheet and hero image while the HTML is still on its way. On Cloudflare, Early Hints are generated from Link headers the site already sends and replayed from cache; details are in Early Hints 103 on Cloudflare. On this site, once HTML was cached at the edge, Early Hints helped mainly on the remaining cache misses, where it overlapped about 90 ms of origin wait with resource fetching.

Measured Impact

Two weeks of field data before and after, mobile:

MeasureBeforeAfter
TTFB p75, all regions640 ms190 ms
TTFB p75, Asia-Pacific1,080 ms240 ms
HTML edge hit ratio59%95%
Landing navigations with ≥1 redirect23%6%
Connection time p75110 ms71 ms
LCP p75, all regions2.1 s1.6 s
Mobile TTFB at the 75th percentile by region before and after Paired bars by region. Europe went from 310 to 140 milliseconds. North America went from 590 to 180 milliseconds. Asia-Pacific went from 1,080 to 240 milliseconds. The largest gains were furthest from the Frankfurt origin. TTFB p75 by region Before After Europe 310 ms 140 ms North America 590 ms 180 ms Asia-Pacific 1,080 ms 240 ms
web-vitals RUM, two weeks per period, mobile navigations only.

Pitfalls & Rollback

  • Optimizing the origin for a cached site. When HTML is served from the edge, origin speed matters only for misses; fix the hit ratio first.
  • no-cache or max-age=0 without s-maxage. Forces an origin round trip on every HTML request at many CDNs.
  • Redirect chains in your own links. Internal links and marketing emails are the easiest hops to remove.
  • Measuring TTFB only in the lab. Lab tests from one region hide the cost for distant readers; use field data by region.
  • Rollback: cache headers, redirect rules and protocol settings are configuration; each can be reverted independently, and a purge restores fresh HTML immediately.

Conclusion

A static site's TTFB is decided by the network, not the server: whether HTML is cached at the edge, how many redirects precede it, how long connection setup takes and how far the origin is from readers. Measure TTFB's parts in the field, cache HTML at the edge with short shared lifetimes and stale-while-revalidate, flatten and move redirects to the edge, enable HTTP/3, and let Early Hints use the time that remains. On this site that took mobile TTFB from 640 ms to 190 ms and LCP from 2.1 s to 1.6 s.

FAQ

What is a good TTFB for a static site?

Google's guidance treats 800 milliseconds at the 75th percentile as a rough threshold for good. A static site served from a CDN edge cache should do much better, typically 100 to 250 milliseconds at p75 on mobile, because no page is generated per request.

Why would static HTML have a slow TTFB?

The usual causes are HTML not being cached at the edge so every request goes to a distant origin, redirect chains before the final URL, slow connection setup from TLS or DNS, and origins such as object storage in a single region. Page generation is rarely the problem on a static site.

Do redirects count toward TTFB?

Yes. In the Navigation Timing API and in CrUX, TTFB is measured from the start of navigation, so every redirect hop before the final response adds its full round trip. A shortened link that redirects twice can double TTFB.

How does TTFB relate to LCP?

LCP cannot happen before the HTML arrives, so TTFB is the first part of every LCP. On static sites with well-optimized images, TTFB is often the largest remaining part of LCP, especially for readers far from the origin.

Filed underHow-tos