Server-Timing Headers in ASP.NET Core: Backend Metrics in Browser DevTools
Maarten Balliauw shows how to add a Server-Timing response header in ASP.NET Core so database and external API timings appear in browser DevTools next to the network timings. The post builds a small middleware, a measurement feature that endpoints can use, and ways to reuse OpenTelemetry Activity data. It also covers gating the header so internal timings are not exposed to every caller.
Key points
- The Server-Timing header uses name;dur=ms;desc="..." entries, and browsers display them without extra tooling.
- The middleware must write the header in OnStarting, because headers cannot be added once the response has begun.
- A per-request feature stored in HttpContext.Features lets any code add timings, with a lock protecting the list.
- If you use OpenTelemetry, put timing tags on the request activity via IHttpActivityFeature, since child span tags are not seen by the middleware.
- Internal timings can leak information about your system, so gate the header with a predicate such as development-only or an authorized role.
- Trailer-based Server-Timing is only read by Firefox in DevTools, so the regular response header is the portable choice.
Browser DevTools show DNS, TLS and the 'waiting for server' bar, but not what the server was busy with during that wait. The Server-Timing response header lets an application publish its own named measurements, such as database or external API durations, and browsers such as Chrome, Edge and Firefox display them alongside their built-in timings. It is a practical way to see backend cost while debugging a request in the browser.
The author builds the feature in layers. A middleware records the total time until the response starts, a per-request feature collects metrics from anywhere in the code, and a small scope helper makes timing a block of code a one-liner. The post also shows how to reuse OpenTelemetry Activity data, either through tags on the request activity or through an ActivityListener, and then gates the header with an options predicate so only development or authorized callers receive the internal timings.
Real-world use
- Finding whether a slow list endpoint is spending its time on a database query or on a pricing service call, straight from the browser during development.
- Exposing timings already recorded on the request Activity in an OpenTelemetry-instrumented ASP.NET Core API, so they also appear in DevTools.
- Enabling internal timing headers in staging only for users in a Diagnostics role.
Why it matters
You can see where a request spends its server time directly in the browser without switching to a tracing tool. Just make sure the header is not exposed publicly in production.