Performance & Uptime

Diagnosing High TTFB: How to Find the Real Cause of a Slow Server Response

A page-speed tool flags your site with a slow "server response time," or you watch the browser sit on a blank screen for a beat before anything paints. That delay has a name — Time To First Byte (TTFB) — and the single most common mistake in fixing it is treating it as one number. TTFB is a sum of several distinct phases, and "high TTFB" tells you almost nothing until you split it apart. People throw a CDN, a caching plugin, or a bigger server at the problem and are baffled when the number barely moves, because they fixed a phase that was never slow.

The takeaway up front: don't optimize TTFB, decompose it. Break the number into DNS, connect, TLS, server wait, and content transfer. One of those phases owns the delay. Measure which one, and the fix becomes obvious instead of a guessing game. This guide gives you the decomposition method, the exact commands, a worked example with real numbers, and the misdiagnoses that waste the most time.

What TTFB actually measures

TTFB is the time from the client sending a request to the first byte of the response body arriving back. That window contains at least five separable stages:

  1. DNS lookup — resolving the hostname to an IP.
  2. TCP connect — the round trip to open the connection.
  3. TLS handshake — negotiating HTTPS (skipped on plain HTTP).
  4. Server wait — the time the server spends thinking before it sends byte one. This is the "thinking time": application code, database queries, upstream API calls.
  5. Content transfer start — the first byte of the body leaving the server.

The first three are dominated by network distance and connection setup. The fourth — server wait — is where application and database problems live. Lumping them together is why a "slow TTFB" diagnosis so often leads to the wrong fix. A site that's slow because the database is doing a full table scan has a completely different cure from a site that's slow because the user is 200ms of latency away from a single-region server.

The decomposition method

You don't need a profiler to find the guilty phase. curl already reports every timing stage. The trick is its -w (write-out) format, which exposes the internal timers most people never look at.

Create a small format file once:

cat > curl-timing.txt <<'EOF'
    dns_lookup:   %{time_namelookup}s
    tcp_connect:  %{time_connect}s
    tls_done:     %{time_appconnect}s
    ttfb:         %{time_starttransfer}s
    total:        %{time_total}s
EOF

Then probe the URL, discarding the body:

curl -w "@curl-timing.txt" -o /dev/null -s https://example.com/slow-page

These timers are cumulative from the start of the request, which is the detail that trips people up. To get the duration of each phase, you subtract the previous one:

  • DNS time = time_namelookup
  • Connect time = time_connect − time_namelookup
  • TLS time = time_appconnect − time_connect
  • Server wait = time_starttransfer − time_appconnect ← the thinking time
  • Transfer = time_total − time_starttransfer

Server wait is the number that matters most for application-level slowness. If it's small but TTFB is still high, your problem is network or connection setup, not your code — and no amount of database tuning will help.

A worked example

Here's a real-shaped probe against a slow page:

dns_lookup:   0.018s
tcp_connect:  0.220s
tls_done:     0.430s
ttfb:         1.310s
total:        1.360s

Decompose it:

  • DNS: 0.018s — fine.
  • Connect: 0.220 − 0.018 = 0.202s — high. That's ~200ms of round-trip latency, the signature of physical distance between client and server.
  • TLS: 0.430 − 0.220 = 0.210s — also high, and consistent with that same 200ms RTT (a TLS handshake costs at least one extra round trip).
  • Server wait: 1.310 − 0.430 = 0.880s — this is the dominant cost. Nearly nine-tenths of a second of the server thinking.
  • Transfer: 0.050s — fine.

The verdict is immediate. There are two problems, but they are not equal. The 200ms RTT (the connect and TLS cost) is a distance problem — a CDN or an edge cache would help there. But the 880ms of server wait dwarfs everything else, so that's where the real win is. If you'd reflexively added a CDN, you'd have shaved maybe 200–400ms off connection setup and left the 880ms untouched, then wondered why the page still felt slow.

Now you isolate the server wait further. Hit the same server locally — from the box itself, over loopback — to remove the network entirely:

curl -w "@curl-timing.txt" -o /dev/null -s http://127.0.0.1/slow-page

If server wait is still ~880ms over loopback, the delay is purely application/database — the network was never the issue. If it drops to near zero, the earlier number included reverse-proxy or upstream latency worth chasing.

Pinning down server wait

Once you know the thinking time is the culprit, narrow it by layer:

  • Compare a static vs. dynamic URL. Request a plain static file (an image, a .txt) and a dynamic page on the same server. If the static file is fast (low server wait) and the dynamic page is slow, the web server and disk are fine — the cost is in your application runtime.
  • Check whether a cache is even being hit. Look at the response headers (curl -I). A page-cache HIT should make server wait tiny; a MISS or BYPASS on every request means your cache isn't doing its job. A surprising share of "slow server" tickets are simply a cache that's misconfigured and never warming.
  • Look at the database. Enable the slow query log and watch it while you reload the page. A single unindexed query doing a full table scan is the classic cause of a fat, otherwise-inexplicable server wait. The fix is an index, not a faster server.
  • Watch for blocking upstream calls. If the page makes a synchronous call to a third-party API on render, that API's latency becomes your TTFB. Move it off the request path (cache it, queue it, or load it async).

The mistakes that waste the most time

Reading TTFB as one number. The root error. "TTFB is 1.3s" is not a diagnosis; "server wait is 0.88s" is. Always decompose first.

Measuring from one location and generalizing. Your TTFB from an office 50ms away looks nothing like a visitor's from another continent. The server wait phase is location-independent, but connect and TLS scale with distance. Test from somewhere representative of real users, and lean on the loopback test to separate distance from compute.

Testing a cached path and declaring victory. The second request to a page is often served warm and fast; the first, cold request is what a real new visitor hits. Test cold paths, and confirm whether you're measuring a cache HIT or a true MISS.

Throwing a CDN at a server-wait problem. A CDN cuts connection-setup and transfer time, and caches static assets brilliantly. It does nothing for the time your application spends generating an uncached dynamic page — unless you specifically configure full-page edge caching. Match the tool to the slow phase.

Buying a bigger server for a single slow query. More CPU and RAM help when you're genuinely resource-bound — sustained high load, memory pressure, swapping. They don't help a request that's idle-waiting on one unindexed query or a slow external API. Confirm you're compute-bound before you upsize.

Edge cases and caveats

  • HTTP/2 and connection reuse mean repeat requests on a warm connection skip DNS, connect, and TLS — so a synthetic single-shot curl can show worse setup numbers than a real browser session that reuses the connection. Interpret the first-request numbers as the cold-start ceiling, not the steady state.
  • Streaming and early flush. Some frameworks send the first byte before the page is fully assembled, which makes TTFB look great while the page still finishes slowly. TTFB measures the first byte, not a usable page — pair it with a full-page or render metric.
  • time_total minus time_starttransfer (the transfer phase) is usually tiny for a small HTML document; if it's large, your response is either huge or being throttled, which is a different conversation from TTFB.

If you're setting up the server these tests run against, the security and stack-install order in our VPS setup guide gives you a clean baseline to measure from — a properly configured box removes a lot of noise before you start chasing milliseconds.

Frequently asked questions

What is a good TTFB?

There's no single correct number, but well-configured sites commonly land server wait in the low hundreds of milliseconds or less for a cached page. More useful than a target is the shape: if server wait dominates your TTFB, you have an application or database problem; if connection setup dominates, you have a distance or network problem. Optimize the phase that's actually large.

Why didn't a CDN fix my TTFB?

A CDN reduces connection-setup and transfer time and caches static assets, but by default it doesn't cache dynamic pages. If your slow phase is server wait — the time your application spends generating the page — a standard CDN leaves it untouched. You'd need full-page edge caching, or you need to fix the server-side cost directly.

How do I tell if it's my code or the network?

Run the same curl timing probe from the server itself over 127.0.0.1. That removes the network entirely. If server wait is still high over loopback, it's your application or database. If it drops, the earlier cost was network distance or a slow upstream — different fix.

My TTFB is fine in testing but users complain it's slow. Why?

You're likely testing a warm cache, a short network path, or a repeat connection that skips DNS/TLS. Real first-time visitors hit cold caches, longer distances, and full handshakes. Test cold paths, from a representative location, and check whether your cache reports a HIT or a MISS on the first request.

Can a slow database really cause high TTFB?

Yes — it's one of the most common causes. The database query runs during server wait, so a single unindexed query doing a full table scan can add hundreds of milliseconds to TTFB on its own. Enable the slow query log, reproduce the slow page, and look for the query. The fix is usually an index, not a bigger server.

Next step

Before you change anything to fix a slow site, measure TTFB by phase. Run the curl timing probe, subtract the cumulative timers to isolate DNS, connect, TLS, server wait, and transfer, then repeat it over loopback to separate compute from network. Once you know which phase owns the delay, the fix picks itself — an index, a cache that actually hits, an edge layer, or a closer server. Decompose first; optimize second.

Ready to run these probes on a server you can actually measure and tune? See what Just-Server offers.

Comments are disabled for this article.