Skip to content
Core Web Vitals specialists
Server & hosting

Server-Timing: Where the Backend Time Actually Goes

TTFB only gives you the sum. The Server-Timing header breaks server time down in the browser into database, cache, application logic and template work.

13 min read TTFBMessungBackend

A high TTFB is a finding without an address. The measurement tells you the server needed 640 milliseconds before the first byte went out – it does not tell you whether that time sat in a database query, in a missed cache, in assembling the template, or in a call waiting on another system. This is exactly where the Server-Timing header comes in: the application reports its own time as named segments to the browser, the browser shows them in DevTools and hands them to JavaScript. A sum turns into line items, a suspicion turns into a measurement – and it happens with real visitors, not only in a lab run. A good TTFB is 0.8 seconds (web.dev) or less; 44 percent (Web Almanac 2025) of mobile websites reached that value. This article covers how the header is structured, how the values are produced on the server, how to collect them from field data and where the limits are. In our server optimisation work it is the first thing we reach for once response time looks suspicious.

Key takeaways

  • TTFB is a sum. Server-Timing breaks it into named segments the server reports itself: database, cache, application logic, template, waiting time on other systems.
  • A good TTFB is 0.8 seconds (web.dev) or less; 44 percent (Web Almanac 2025) of mobile websites were in that range in 2025.
  • Every segment carries a name, plus an optional duration via dur and a description via desc (W3C). If the duration is missing, the interface reports 0 (W3C) instead of an error.
  • In the field the values are readable via PerformanceServerTiming: Chrome from version 65 (MDN), Firefox from 61 (MDN), Safari from 16.4 (MDN). From Chrome 136 (Chrome for Developers) the Summary tab of the Performance panel shows server timings too.
  • The header is public and restricted to the same origin; other origins need Timing-Allow-Origin (W3C). Short, neutral names are therefore a requirement, not a nicety (MDN).

TTFB gives you the sum, not the line items

Time to First Byte measures the span from sending the request to the first byte of the response. As a rough guide, 0.8 seconds (web.dev) or less counts as good, and it turns poor above 1.8 seconds (web.dev). In the field the distribution looks like this: 44 percent (Web Almanac 2025) of mobile websites reached a good TTFB in 2025, 17 percent (Web Almanac 2025) were in the poor range, and on desktop 55 percent (Web Almanac 2025) were good. A year earlier the mobile share stood at 42 percent (Web Almanac 2025) – things are moving, but slowly. What these numbers do not tell you is the cause. The value is a sum of network path, connection setup and the time the server needs for its response. How that sum splits at the coarse level is covered in the analysis of server response time; this article goes one level deeper and divides the server share further.

The server share is awkward precisely because it sits ahead of everything else. Largest Contentful Paint counts as good up to 2.5 seconds (web.dev), measured at the 75th percentile (web.dev) of page loads – and TTFB is the first of its four phases. Every millisecond the server spends is passed forward: it delays the start of loading the main resource, delays its end, and with it the moment the visitor sees something. How that chain divides in detail is described in the article on the four LCP subparts; how the metric fits into the wider Core Web Vitals picture is covered there as well. Cleaning up the frontend without touching the server base moves the bottleneck rather than resolving it.

A header that reports – the measuring is still yours

The header does not produce a measurement. It only transports what the application recorded itself. If you set no timestamps around query, cache and template, you get an empty header back. So the effort sits in the measuring points, not in the transport – and it is a one-off: once the points are in place, every single response carries its own breakdown, including responses to real visitors. That is what separates the header from a lab run that only shows how the page behaves in a clean test environment.

How the header is structured

Server-Timing is a response header carrying a list of metrics separated by commas. Each metric has a name, plus two optional parameters the specification defines: dur for the duration and desc for a description (W3C). The duration is a DOMHighResTimeStamp and usually represents milliseconds – the specification explicitly calls that a recommendation rather than an enforceable rule (W3C). Names and descriptions should stay short, because every character travels in the header of every single response (MDN). A segment without a duration is allowed and then carries only its name – useful for states such as a cache hit or miss, which have no meaningful time value.

antwort.http
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Server-Timing: db;dur=210, cache;desc="miss";dur=40, app;dur=180, tpl;dur=95
Timing-Allow-Origin: https://www.example.com
Cache-Control: private, no-store

The browser takes the values without judging them. If dur is missing, the interface returns 0 (W3C) for that segment – so the entry does not disappear, it simply has no duration. That is practical as long as you know it, and misleading as soon as someone sums the segments up and wonders about the gap. The sensible pattern: carry states in desc, set durations only where time was actually spent, and choose names that stay understandable in a report without further explanation.

Producing the segments on the server

The implementation is unspectacular: before and after every section that can cost time you take a high-resolution timestamp, the difference goes into a variable, and shortly before the response is sent the header is assembled from those variables. Only the order matters – the header has to be set before the first piece of the body leaves the process. Applications that start output early can fall back on the trailer variant the specification provides; it is, however, evaluated only in the browser's developer tools and not through the JavaScript interface (MDN). For field measurement it is therefore of no use.

kategorie.php
// wrap the work, not the call
$t0 = hrtime(true);
$items = catalog_read($categoryId);
$dbMs = (hrtime(true) - $t0) / 1e6;

$t1 = hrtime(true);
$block = cache_read('nav:' . $language, $hit);
$cacheMs = (hrtime(true) - $t1) / 1e6;

$t2 = hrtime(true);
$html = render_template('category', $items, $block);
$tplMs = (hrtime(true) - $t2) / 1e6;

// set the header before the body starts
header(sprintf(
    'Server-Timing: db;dur=%.1f, cache;desc="%s";dur=%.1f, tpl;dur=%.1f, app;dur=%.1f',
    $dbMs,
    $hit ? 'hit' : 'miss',
    $cacheMs,
    $tplMs,
    (hrtime(true) - $t0) / 1e6
));

Three things pay off in practice. First, the measuring points belong around the work and not around the function call: a query that is only executed when the result is read reports zero milliseconds and pushes its time into the next segment. Second, segments should not overlap – otherwise their sum exceeds the application's own time and nobody trusts the numbers any more. Third, a total segment for application time helps as a cross-check: if a gap remains between it and the sum of the individual segments, unmeasured work is sitting there. How that runtime divides further down is covered in the article on PHP runtime tuning with OPcache and workers.

From the tools into the field data

In the lab the header is visible right away: the network view of the developer tools shows the reported segments in the timings tab, and from Chrome 136 (Chrome for Developers) they also appear in the Summary tab of the Performance panel. The second route is the more interesting one: every response carrying the header fills the serverTiming property of the corresponding entry in the Performance interface. That is available in Chrome from version 65 (MDN), in Firefox from 61 (MDN) and in Safari from 16.4 (MDN) – coverage is therefore broad enough for a survey that describes more than a single browser family.

messwerte.js
const [navigation] = performance.getEntriesByType('navigation');
const segments = [];

for (const entry of navigation.serverTiming) {
  segments.push({
    name: entry.name,
    duration: Math.round(entry.duration),
    note: entry.description
  });
}

// send it to your own collector together with the context
navigator.sendBeacon('/metrics/', JSON.stringify({
  pageType: document.body.dataset.pageType,
  loggedIn: document.body.dataset.loggedIn === '1',
  ttfb: Math.round(navigation.responseStart),
  segments
}));

The collected value belongs to a real page load and can be joined with everything you record anyway: page type, login state, region, device. That answers questions a lab run cannot – such as whether the category page is slow only for logged-in users because the page cache does not apply there. Note that in some browsers the interface is restricted to secure contexts and therefore requires HTTPS (MDN). How to read field and lab measurements side by side is covered in the article on field data and performance budgets. And because sending the metrics can itself cost speed, it is worth looking at fetchLater and deferred beacons.

Database

The most common single segment. One name per query group is enough: db for the main query, dbn as a segment without a duration for the number of queries. If that number grows with category depth or a filled cart, you see it in the field immediately. The levers behind it are described in the article on query optimisation in the shop.

Cache

One segment with desc for hit or miss and a duration for the access itself. Only then can you calculate the hit rate in the field instead of estimating it – and only then do you see which page types systematically miss. Which layers are worth using is shown in the article on caching strategies.

Template and output

The time spent assembling the response. It grows with the number of building blocks and with every fragment loaded only at render time. A separate segment cleanly divides it from data retrieval and shows when the template, not the query, is the bottleneck.

Which segments are actually worth it

Five to seven segments are usually enough. More names make the header longer and the reporting harder to read without improving the diagnosis. This split has proven itself: database, cache with its state, application logic, template, waiting time on external systems and a total segment as a cross-check. If a delivery network sits in front, segments often arrive ready-made, though very unevenly: the Web Almanac survey shows networks that set the header on 100 percent (Web Almanac 2025) of the requests they serve, while others add it only after explicit configuration. How delivery network and origin server interact is covered in the article on edge caching for shops; how we turn that into a procedure is on the page about TTFB optimisation.

Question from operationsTTFB aloneTTFB with Server-Timing
Where exactly is the time spent?unknownnamed per segment
Was the cache hit?guesswork onlycarried as a state in the header
Only logged-in users affected?cannot be separatedseparable via field data
Effect of a changethe sum shiftsthe affected segment drops
Effort to introducenoneone-off measuring points
Statement about real visitorsyes, but without a causeyes, with a cause

The core in one sentence

A TTFB without a breakdown is an alarm without a location: it reports that something takes time, but not what. Server-Timing turns the alarm into a list – and the list into an order you can work through.

Limits, exposure and pitfalls

The header is public. Anyone who sees the response also sees the segments – and with them a piece of your internal architecture. The specification points out explicitly that access to the interface is therefore restricted to the same origin by default and that other origins are only granted access through Timing-Allow-Origin (W3C). MDN states the flip side directly: the header may expose potentially sensitive application and infrastructure information (MDN). In practice that means short, neutral names, no table names, no host names, no error strings. If you need more depth, serve the detailed breakdown only to logged-in staff and give public responses the coarse one – two variants of the same header, controlled in one place in the code.

Three pitfalls we run into regularly

First, the header measures only the time inside the application. If a request waits in the worker queue beforehand, that appears nowhere; it needs a segment the web server itself sets. Second, with early hints enabled the response begins before processing has finished – pure server time is then read more reliably from the header than from TTFB alone; how that interacts is described in the article on early hints with status 103. Third, the header travels on every single response: short names and terse descriptions are not a cosmetic question (MDN).

Getting started in your own application

  1. Identify the slowest page type: home, category, detail, search or cart. Start measuring where TTFB in the field is highest.
  2. Place four measuring points: database, cache, application logic, template. Add a total segment for application time as a cross-check.
  3. Ship the header to a preview environment first and read it back in the developer tools: are the segments plausible, does their sum match application time?
  4. Enable the header in production – with neutral names and without descriptions that give internals away.
  5. Collect the values through the Performance interface and send them to your own collector together with page type, login state and device.
  6. Evaluate after two weeks: which segment contributes most to the slow quarter of page loads? That is where the work starts, and that is where you re-measure after the change.

The effort for the first pass is typically a few hours, because the measuring points sit in four or five places in the code and the header in one. The return usually shows up on the first look at the field data: often the largest segment is somewhere other than the team assumed – and the optimisation that has been planned for months turns out to address the second smallest. That is how a performance analysis starts with us when TTFB looks suspicious and the cause is open. Which levers come into question afterwards depends on the finding; an overview of how we work is on the page about our services. Once the server base is solid, the work continues in the frontend – for instance by finding and removing unused CSS.

The difference between "the server is slow" and "the category query is the largest segment and it mainly hits logged-in users" is not a measurement detail. It is the difference between a discussion and a task.

Project experience from server optimisation work for shops and portals

Sources and Studies

This article draws on data from web.dev, MDN Web Docs, the W3C, the HTTP Archive Web Almanac 2025 and Chrome for Developers. The figures quoted refer to the state of each publication at the time it was released.

Related Articles