Server-Timing observability response
Communicates server-side timing metrics for a request — how long the database took, cache lookup, rendering, and more.
What it does
Server-Timing lets the server communicate performance metrics about a request back to the client. You define named timing entries — how long the database query took, whether you got a cache hit, how long the template rendered — and they show up directly in Chrome DevTools' Network panel under the Timing tab.
It's the cleanest way to expose server-side performance data to your frontend team without them having to dig through logs or set up separate tooling.
Syntax
Server-Timing: <name>[;dur=<ms>][;desc="<description>"]
Server-Timing: <metric1>, <metric2>, <metric3>
Multiple metrics are comma-separated. Each metric has:
name— required, identifier for the metricdur— duration in milliseconds (floating point OK)desc— human-readable description (quoted string)
Examples:
Server-Timing: db;dur=23.4
Server-Timing: db;dur=23.4;desc="Database query"
Server-Timing: cache;dur=0.3;desc="Cache hit", db;dur=23.4;desc="User query", render;dur=4.1;desc="Template render"
Server-Timing: miss, db;dur=53, app;dur=47.2
Reading it in Chrome DevTools
Open DevTools → Network tab → click any request → Timing tab. Scroll to the bottom and you'll see a Server Timing section with a bar chart of each metric. Zero dur metrics (like miss or hit) show as labels without bars.
This is genuinely useful for performance debugging — you can see at a glance whether your slow API endpoint is slow because of the DB, the cache layer, or your application logic, without touching logs.
Common metrics to instrument
Server-Timing: cache;dur=0.1;desc="Cache lookup",
db;dur=45.2;desc="Primary DB query",
db_replica;dur=12.3;desc="Replica read",
render;dur=3.8;desc="Template",
total;dur=62.1;desc="Total server time"
Good candidates to measure:
- Database queries (broken out by query or replica)
- Cache lookups (Redis, Memcached)
- External API calls
- Authentication checks
- Template/view rendering
- Queue job dispatch
Implementing it in Laravel
// Middleware approach — wrap the full request lifecycle
class ServerTimingMiddleware
{
public function handle(Request $request, Closure $next)
{
$start = microtime(true);
$response = $next($request);
$total = round((microtime(true) - $start) * 1000, 2);
$response->headers->set(
'Server-Timing',
"app;dur={$total};desc=\"Application\""
);
return $response;
}
}
// In a controller — more granular
public function show(int $id): JsonResponse
{
$dbStart = microtime(true);
$user = User::with('posts')->findOrFail($id);
$dbDur = round((microtime(true) - $dbStart) * 1000, 2);
$cacheStart = microtime(true);
$stats = Cache::remember("user:{$id}:stats", 300, fn() => $this->computeStats($user));
$cacheDur = round((microtime(true) - $cacheStart) * 1000, 2);
return response()->json($user)->withHeaders([
'Server-Timing' => "db;dur={$dbDur};desc=\"User query\", cache;dur={$cacheDur};desc=\"Stats\""
]);
}
Security: don't leak sensitive info
Server-Timing is visible to anyone who can see the response headers — including users and potential attackers. Be careful:
- Don't use metric names that reveal architecture —
auth0_lookuporstripe_apitells attackers what services you depend on - Consider stripping on production for unauthenticated endpoints — or only expose it for authenticated/internal requests
- Duration alone can be a timing oracle — a login endpoint where
db;dur=45vsdb;dur=2tells an attacker whether a username exists
For internal tooling or authenticated APIs, full verbosity is fine. For public endpoints, keep names generic (db, cache, ext) and consider toggling based on environment.
Server-Timing in trailers
Server-Timing is one of the few headers that makes genuine sense as a chunked trailer — you can compute total request time after the response body is fully sent and append it as a trailer. Browser DevTools will still pick it up.
Transfer-Encoding: chunked
Trailer: Server-Timing
<chunks>
0
Server-Timing: total;dur=234.5
Common mistakes and gotchas
Forgetting to remove it in production. Fine to leave on for authenticated APIs. A bad idea on public endpoints where it reveals infrastructure timing.
Measuring the wrong thing. If you measure from inside a middleware after routing has already happened, you're missing routing time. If you measure inside the controller, you're missing middleware time. Instrument at the right layer for what you actually want to know.
Not measuring external calls. The biggest source of slowness is almost always external: third-party APIs, email services, payment gateways. Those are the most valuable things to put in Server-Timing.
Real-world examples
API response with full breakdown:
HTTP/1.1 200 OK
Content-Type: application/json
Server-Timing: cache;dur=0.4;desc="Redis", db;dur=34.2;desc="Postgres", render;dur=1.1;desc="Serializer"
Cache miss vs hit:
# Miss:
Server-Timing: miss, db;dur=67.3
# Hit:
Server-Timing: hit, cache;dur=0.2
FAQ
Does Server-Timing work in all browsers?
Chrome, Firefox, and Edge all display it in DevTools. Safari added support in Safari 16. It's universally readable via the Performance API in JavaScript too: performance.getEntriesByType('resource') — each entry has a serverTiming array.
Can JavaScript read Server-Timing values?
Yes — via the Resource Timing API:
const entries = performance.getEntriesByType('resource');
entries.forEach(entry => {
entry.serverTiming.forEach(metric => {
console.log(metric.name, metric.duration, metric.description);
});
});
This only works if the response includes Timing-Allow-Origin: * (or your origin) — otherwise browser security blocks cross-origin timing data.
Should Server-Timing replace my APM tool?
No — it complements it. APM tools (Datadog, New Relic, Sentry) give you aggregated metrics, historical data, alerting, and traces across multiple services. Server-Timing gives your frontend developers real-time per-request visibility in their browser. Different audiences, different use cases. Use both.
Fun fact
Server-Timing was spec'd by the W3C Web Performance Working Group in 2017 as part of a broader push to give developers better visibility into server performance without custom tooling. Before it existed, the common workaround was embedding timing data in response bodies (JSON fields like _timing: { db: 45 }) or custom headers like X-Response-Time. Server-Timing standardised all of this into a single header with browser DevTools integration — one of those rare cases where a web standard genuinely replaced a mess of ad-hoc conventions cleanly.