WSS
Web Specification Studio Home
On this page
PerformanceOptionalUpdated

Server-Timing header

Server-Timing surfaces backend metrics - database time, cache hits, edge processing - in browser DevTools and to RUM via the PerformanceServerTiming API. Send it when you measure server-side latency, and keep the values free of sensitive infrastructure detail.

What it is

Server-Timing is an HTTP response header that carries one or more performance metrics about how the server produced the response. Each metric has a name and optional dur (duration in milliseconds) and desc (label):

Server-Timing: db;dur=53, cache;desc="hit";dur=0.4, edge;dur=12

The metrics show up in the browser DevTools network panel against the request, and - for same-origin responses - are readable from JavaScript through the PerformanceServerTiming interface, so real-user monitoring (RUM) can collect them alongside Core Web Vitals.

This site ships it. Every response sets Server-Timing: edge;desc="…";dur=…, measured in the Cloudflare Pages middleware (functions/_middleware.ts). Open DevTools → Network, click the document request, and read the Timing tab.

Why it matters

Browser timing APIs measure the request from the client’s side - DNS, connection, time to first byte. They cannot see why the server took 600 ms. Server-Timing is the only standard channel for the server to attribute that time: how much was the database, how much was a cache miss, how much was rendering. It turns an opaque TTFB into a breakdown you can act on, both interactively in DevTools and in aggregate through RUM.

How to implement

Emit the header from whatever produces the response - origin server, edge function, or CDN. Measure the work, then format the metrics:

Server-Timing: app;desc="render";dur=42, db;dur=53, cache;desc="miss"

Keep names and descriptions short - they are shipped on every response, so verbosity is pure overhead. A metric may carry just a name (cache;desc="hit"), a duration, or both.

To read the metrics from JavaScript for RUM:

const [nav] = performance.getEntriesByType("navigation");
for (const { name, duration, description } of nav.serverTiming) {
  // send {name, duration, description} to your analytics endpoint
}

Common mistakes

  • Expecting cross-origin metrics to appear in JavaScript. PerformanceServerTiming is same-origin only. For a CDN or third-party host, the response must send Timing-Allow-Origin naming your origin, or nav.serverTiming is empty - the DevTools display still works, but automated collection silently does not.
  • Leaking infrastructure detail. The header is visible to anyone. Internal service names, query counts, and host identifiers are an information-disclosure surface; the spec warns about this. Send coarse labels publicly, or gate richer metrics behind authentication.
  • Sending it as a trailer expecting the Fetch API to read it. Server-Timing as an HTTP trailer is consumed only by DevTools, not by fetch() or the Performance API.

RUM Beacon Integration & Cross-Origin Security

Extracting maximum value from Server-Timing in production RUM pipelines requires managing cross-origin security boundaries:

  • Configuring Timing-Allow-Origin for CDNs: When assets or API responses are served from a sub-domain or third-party CDN (e.g. cdn.example.com fetching on example.com), the browser strips serverTiming entries from the JavaScript Performance API unless the server responds with Timing-Allow-Origin: https://example.com (or Timing-Allow-Origin: *).
  • Correlating Server TTFB with Client LCP: In RUM metrics, combine Server-Timing durations with PerformanceObserver navigation entries to calculate true network transit overhead (TTFB - Server-Timing total = Network Latency).
  • Privacy Stripping in Production Builds: Ensure backend frameworks (Node.js, Go, Rust) automatically strip high-resolution microsecond execution metrics (dur=0.00123) or internal SQL query tags on public production builds to prevent side-channel timing attacks.

Verification

  • Inspect HTTP response headers via curl -i https://example.com to verify Server-Timing structure.
  • Open DevTools → Network → Timing tab to verify visual breakdown of server execution phases.
  • Run performance.getEntriesByType('navigation')[0].serverTiming in browser console to confirm RUM access.

Related topics

Sources & further reading