The Server-Timing Header Explained
The Server-Timing HTTP header exposes backend performance metrics — database time, cache hits — directly in the browser's DevTools network panel.
The Server-Timing header is an HTTP response header that lets a backend report its own performance breakdown — how long a database query took, whether a cache was hit, how long a downstream API call blocked the response — directly to the browser. That data shows up in the DevTools Network panel’s Timing tab, right alongside the browser-side timing the client already measures, closing a long-standing blind spot: front-end performance tools can see how long a request took over the wire, but not what happened on the server while it waited.
What the header looks like
A response carries one Server-Timing header with one or more comma-separated metrics:
Server-Timing: db;dur=45.2, cache;desc="hit", auth;dur=12.1
Each entry has a short name, an optional dur (duration in milliseconds), and an optional desc (a human-readable description). Multiple metrics can also be split across repeated headers, which some server frameworks find easier to emit incrementally:
Server-Timing: db;dur=45.2
Server-Timing: cache;desc="hit"
Setting it from your backend
In a Node.js server, this is just another response header:
app.get("/api/orders", async (req, res) => {
const dbStart = performance.now();
const orders = await db.query("SELECT * FROM orders WHERE user_id = $1", [req.userId]);
const dbDur = performance.now() - dbStart;
res.setHeader("Server-Timing", `db;dur=${dbDur.toFixed(1)}`);
res.json(orders);
});
For multiple stages, build up the header value as you go:
const timings = [];
function mark(name, durationMs) {
timings.push(`${name};dur=${durationMs.toFixed(1)}`);
}
// ... mark("db", dbDur); mark("cache-lookup", cacheDur); ...
res.setHeader("Server-Timing", timings.join(", "));
The values are visible to anyone who opens DevTools, so avoid putting anything sensitive in the desc field — internal hostnames, query text, or user data have no place in a header meant for performance debugging.
Reading it in DevTools
Open the Network panel, select a request, and go to the Timing tab. Below the browser’s own phases (queuing, DNS, connect, TLS, request sent, waiting, content download), each Server-Timing entry appears as its own labeled segment. This is the piece that’s normally invisible from the client: the “waiting” phase in a browser’s timing view is a single opaque bar, and Server-Timing is what breaks it open into “45ms in the database, 12ms in auth, the rest in application logic.”
Why this matters for diagnosing slow requests
Without Server-Timing, a slow API response looks identical from the browser whether the bottleneck is a missing database index, a cold cache, an overloaded downstream service, or serialization of a large payload — all of it collapses into one number. That ambiguity is exactly what makes Time to First Byte hard to act on: a high TTFB tells you the server was slow, not why.
Server-Timing turns that single number into a breakdown you can act on directly, and it’s the natural complement to HTTP caching headers: a cache;desc="miss" entry tells you immediately whether a slow response was a cache miss or a genuinely slow computation, without cross-referencing server logs.
Aggregating it beyond DevTools
DevTools is useful for one request at a time, but Server-Timing values are also exposed to JavaScript through the Resource Timing and Navigation Timing APIs, via PerformanceResourceTiming.serverTiming:
const [entry] = performance.getEntriesByName("/api/orders", "resource");
for (const { name, duration, description } of entry.serverTiming) {
console.log(name, duration, description);
}
This is how real-user-monitoring tools turn Server-Timing into aggregate dashboards — sampling actual user requests in production rather than relying on synthetic tests, and correlating backend timing with the Core Web Vitals those same requests contributed to.
A word on overhead and precision
Timing your own code with performance.now() calls adds negligible overhead — microseconds against a response that’s already taking milliseconds — but it’s still worth being deliberate about which stages you instrument. Wrapping every function call in a timing mark produces a header so cluttered it defeats the purpose; the useful granularity is usually the handful of stages that plausibly explain most of a slow response: the database, an external API call, serialization, and any cache lookups. That mirrors how you’d approach the critical rendering path on the client side — measure the phases that matter, not every function in the call stack.
Server-Timing vs client-side timing APIs
| Client-side (Navigation/Resource Timing) | Server-Timing | |
|---|---|---|
| Measures | Network and browser phases | Backend processing phases |
| Set by | Browser automatically | Application code, per response |
| Visible in | DevTools Timing tab, performance API | DevTools Timing tab, performance API |
| Granularity | DNS, connect, TLS, request, response | Whatever the backend chooses to report |
| Requires code changes | No | Yes, on the server |
The two are complementary rather than competing: client-side timing tells you where time went across the network, and Server-Timing tells you where it went once the request reached your infrastructure.
The takeaway
The Server-Timing header is a small addition to a response — a comma-separated list of named durations — that pays off every time someone has to debug a slow endpoint. It turns an opaque “waiting” phase in DevTools into a labeled breakdown of database time, cache status, and downstream calls, without adding logging infrastructure or a tracing system. Instrument the handful of stages that plausibly explain a slow response, keep the descriptions free of sensitive detail, and the next slow-request investigation starts in the Network panel instead of the server logs.
Keep reading
The Lycoris Team · · 5 min read Head-of-Line Blocking Explained: HTTP/1.1, HTTP/2, QUIC
Head-of-line blocking is when one stalled item holds up everything queued behind it. How it affects HTTP/1.1, HTTP/2 over TCP, and how QUIC fixes it.
Takina · · 4 min read What Is WebTransport? A Guide to the API
WebTransport is a browser API for low-latency, bidirectional client-server messaging over HTTP/3. How it works and when to reach for it.
Takina · · 4 min read HTTP Range Requests and Partial Content, Explained
HTTP range requests let a client ask for just part of a resource, enabling video seeking, resumable downloads, and partial file fetches over HTTP.