php artisan make:middleware AddServerTiming writes a class to app/Http/Middleware (add --pest or --phpunit for a test) whose handle only returns $next($request). Add use Illuminate\Support\Str; and fill it in: the before part starts a clock and adopts an incoming X-Request-Id or mints a UUID; the after part writes the standard Server-Timing header, shown in browsers' Timing tab.
public function handle(Request $request, Closure $next): Response
{
$start = hrtime(true); // before
$id = $request->header('X-Request-Id') ?: (string) Str::uuid();
$request->headers->set('X-Request-Id', $id);
$response = $next($request); // the rest of the app
$ms = (hrtime(true) - $start) / 1e6; // after
$response->headers->set('Server-Timing', sprintf('app;dur=%.1f', $ms));
$response->headers->set('X-Request-Id', $id);
return $response;
}Registered globally (Registering Middleware), it tags even the /up health route, and a caller's own ID comes back unchanged:
curl -si -H 'X-Request-Id: order-1042' http://127.0.0.1:8304/up -o /dev/null -D -HTTP/1.1 200 OK Host: 127.0.0.1:8304 Connection: close X-Powered-By: PHP/8.5.4 Cache-Control: no-cache, private Date: Wed, 23 Sep 2026 07:21:35 GMT Content-Type: text/html; charset=utf-8 Server-Timing: app;dur=33.4 X-Request-Id: order-1042
Always return a Response, since null or a string fails the return type; put after-work after $next; and keep per-request state out of properties, because under Octane 4,043 one instance serves many requests (Octane). Constructor dependencies are injected from the service container.