API rate limiter returns 429 without Retry-After header

Medium staging Laravel API
4
views
2
votes

The API rate limiting middleware rejects requests with a 429 Too Many Requests response, but the response does not include the standard Retry-After header. Clients that rely on this header to implement backoff logic retry immediately and get banned.

Steps to reproduce

  1. Enable the default throttle middleware on any API route (throttle:60,1)
  2. Send more than 60 requests within one minute
  3. Inspect the 429 response headers — Retry-After is missing

Expected behavior

The 429 response should include a Retry-After header indicating how many seconds the client must wait before retrying.

A
abdelhamid
Jul 28, 2026

Solutions

1
2
A
abdelhamid Jul 28, 2026

The ThrottleRequests middleware only adds the Retry-After header when the X-RateLimit-* headers are enabled in its constructor. Make sure the throttle middleware is registered with headers enabled, or add the header manually in the exception handler.

// app/Http/Kernel.php
'api' => [
    'throttle:api',
    \Illuminate\Routing\Middleware\SubstituteBindings::class,
],

// app/Exceptions/Handler.php — fallback: add the header manually
$this->renderable(function (ThrottleRequestsException $e, $request) {
    return response()->json(['message' => 'Too many requests'], 429)
        ->header('Retry-After', $e->getHeaders()['Retry-After'] ?? 60);
});

After this change, curl -i on a throttled route shows Retry-After: 60 in the 429 response.

Have a solution to share?

Sign in to submit a solution