Time to First Byte (TTFB) is the time between the crawler sending the request and your server returning the first byte. It’s the floor for every other speed metric — you can’t have a 2-second LCP if TTFB alone is 2.5 seconds.
Google’s documented thresholds (CrUX, 75th percentile):
- Good: TTFB < 800 ms
- Needs improvement: 800 ms – 1800 ms
- Poor: > 1800 ms
In practice, sustained TTFB above ~600 ms also causes Google to throttle crawl rate. That alone is a discovery problem before it’s a ranking problem.
How to detect it
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://example.com/
The number that matters most for crawlers is time_starttransfer minus time_appconnect — that’s pure server work, network neutralized.
Three signals worth checking together:
- CrUX TTFB at 75th percentile (PageSpeed Insights, real-user data)
- Sampled crawler TTFB (Search Console → Settings → Crawl stats → Avg response time)
- Lab TTFB (curl from the same region as your origin)
A gap between CrUX and lab means real users hit different edges or different cache states. A gap between Search Console and lab means Googlebot is hitting cold paths.
The fix — find the actual cost
Sources of slow TTFB, ordered by frequency:
- Database query inside the request path — N+1 queries, missing indexes, joins on cold tables.
- Cold starts on serverless — first request to an idle function pays minutes-old container init.
- Synchronous third-party calls — calling an analytics or auth API in the request handler, blocking on its response.
- Origin without a CDN — every request traverses the public internet to one machine.
- Render fan-out — the page makes 50 internal HTTP calls to build itself.
Universal — add a CDN cache layer
The cheapest TTFB fix is to serve already-rendered HTML from an edge cache. Set explicit cache headers:
Cache-Control: public, s-maxage=300, stale-while-revalidate=86400
s-maxage is for shared caches (CDNs). stale-while-revalidate lets the CDN serve a slightly stale copy while it refreshes — TTFB stays low even when the page changes.
For pages that legitimately can’t be cached (logged-in user pages, personalized homepages), the fix is to make the origin work faster, not to cache.
WordPress
Order of impact:
- Add an object cache (Redis or Memcached) —
wp-config.phpconstantWP_CACHE, plus a drop-in like Redis Object Cache. - Add a full-page cache (WP Rocket, LiteSpeed Cache, or Cloudflare APO).
- Audit plugins —
query-monitorwill show you the slowest DB queries per request. Disable or replace plugins that scanwp_optionson every load.
Shopify
Shopify’s origin is already fast. Slow TTFB on Shopify is almost always one of:
- A theme that makes synchronous calls to a third-party app from
theme.liquid. - A bloated app that injects script and waits for it to evaluate before the page completes.
Audit with ?view=performance query params on a few key pages, or use the Shopify performance debugger in the admin.
Next.js (App Router)
Use static generation where possible (force-static route segment config). For dynamic pages, enable PPR (Partial Pre-Rendering) or ISR via revalidate:
// app/products/[slug]/page.tsx
export const revalidate = 3600; // ISR — refresh every hour
export default async function Page({ params }: { params: { slug: string } }) {
const product = await getProduct(params.slug);
return <Product product={product} />;
}
For RSC pages, audit your await chain. Parallelize independent fetches:
// before — serial, slow
const product = await getProduct(slug);
const reviews = await getReviews(slug);
// after — parallel
const [product, reviews] = await Promise.all([
getProduct(slug),
getReviews(slug),
]);
Nginx (as a microcache)
A 30-second microcache in front of a slow origin is a one-line fix that hides almost any spike:
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=mc:10m max_size=1g;
server {
location / {
proxy_pass http://origin;
proxy_cache mc;
proxy_cache_valid 200 30s;
proxy_cache_use_stale updating error timeout;
add_header X-Cache $upstream_cache_status;
}
}
Crawlers hit the cache almost every time. Users hit the cache during traffic spikes. TTFB drops to single-digit milliseconds.
Pitfalls
Don’t optimize the wrong layer. TTFB at the browser includes DNS, TLS, network, and server. If your TTFB is 1.2s and 800ms is TLS handshake from a faraway region, optimizing your database won’t help. Measure first.
Don’t cache personalized content blindly. A logged-in dashboard cached for an anonymous bot is a data leak. Vary by Authorization header or by session cookie.
Don’t ignore third-party scripts at the document level. A <script src="…"> in <head> that blocks parsing doesn’t move TTFB, but it gates every other metric. Defer or lazy-load anything below-the-fold.
stale-while-revalidate is your friend. Most pages don’t need to be perfectly fresh. Letting the CDN serve a 5-minute-old version keeps TTFB low even during a refresh.
Fix at the edge with Serpwise
When the origin is genuinely slow and re-architecting is a quarter-long project, the edge does the work for you.
Serpwise caches HTML at the edge with full control over the cache key (per URL, per query param, per user agent), respects Cache-Control headers from your origin, and serves stale content during origin outages. TTFB drops to whatever your edge is, regardless of what your origin is doing.
See pricing or run a free AI visibility audit.