Saltar al contenido principal

Cómo funciona la facade RateLimiter

El rate limiting de Laravel se compone de la clase Illuminate\Cache\RateLimiting\Limit y de la facade RateLimiter. Internamente guarda contadores en el driver de caché (por defecto file o Redis) y sigue el número de peticiones. El middleware throttle ejecuta la closure definida con RateLimiter::for() para la petición entrante y, si se supera el límite, devuelve 429 Too Many Requests.

Definir limitadores personalizados en AppServiceProvider

La configuración del rate limiting se hace en el método boot() de App\Providers\AppServiceProvider.
<?php

namespace App\Providers;

use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        RateLimiter::for('api', function (Request $request) {
            return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip());
        });
    }
}
El primer argumento de RateLimiter::for() es el nombre del limitador que referenciarás desde el middleware throttle. La closure del segundo argumento debe devolver una instancia de Illuminate\Cache\RateLimiting\Limit.

Rate limits por usuario, por IP y por plan

Diferenciar entre usuarios autenticados e invitados

RateLimiter::for('uploads', function (Request $request) {
    return $request->user()
        ? Limit::perHour(100)->by($request->user()->id)
        : Limit::perHour(10)->by($request->ip());
});

Limitar según el plan del usuario

RateLimiter::for('api', function (Request $request) {
    $user = $request->user();

    if (! $user) {
        return Limit::perMinute(30)->by($request->ip());
    }

    return match ($user->plan) {
        'enterprise' => Limit::none(),
        'pro'        => Limit::perMinute(500)->by($user->id),
        default      => Limit::perMinute(60)->by($user->id),
    };
});

Limitación global por IP

Aplica throttling a nivel de IP, con independencia del endpoint concreto.
RateLimiter::for('global', function (Request $request) {
    return Limit::perMinute(1000)->by($request->ip());
});

Combinar varios límites

Si devuelves un array, se evalúan todos los límites. Cuando cualquiera se supera, se devuelve 429.
RateLimiter::for('login', function (Request $request) {
    return [
        Limit::perMinute(10)->by($request->ip()),
        Limit::perMinute(5)->by($request->input('email')),
    ];
});
Si defines varios límites con el mismo valor by, añade prefijos para que las claves no colisionen.
RateLimiter::for('uploads', function (Request $request) {
    return [
        Limit::perMinute(10)->by('minute:' . $request->user()->id),
        Limit::perDay(1000)->by('day:' . $request->user()->id),
    ];
});

Middleware throttle y nombre del limitador personalizado

Pasa al middleware throttle el nombre del limitador definido.
use Illuminate\Support\Facades\Route;

Route::middleware(['throttle:api'])->group(function () {
    Route::get('/user', function () { /* ... */ });
    Route::post('/posts', function () { /* ... */ });
});

Registro en bootstrap/app.php

Desde Laravel 11, los middlewares se gestionan desde bootstrap/app.php.
use Illuminate\Foundation\Application;

return Application::configure(basePath: dirname(__DIR__))
    ->withRouting(
        web: __DIR__ . '/../routes/web.php',
        api: __DIR__ . '/../routes/api.php',
        apiPrefix: 'api',
    )
    ->withMiddleware(function (\Illuminate\Foundation\Configuration\Middleware $middleware): void {
        $middleware->throttleApi('api');
    })
    ->create();

Ejemplo de aplicación en rutas de API

1

Define los limitadores

Define varios limitadores en AppServiceProvider.
public function boot(): void
{
    // Acceso general a la API
    RateLimiter::for('api', function (Request $request) {
        return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip());
    });

    // Subidas de archivos
    RateLimiter::for('uploads', function (Request $request) {
        return $request->user()?->isPro()
            ? Limit::perHour(500)->by($request->user()->id)
            : Limit::perHour(50)->by($request->user()?->id ?: $request->ip());
    });

    // Intentos de login
    RateLimiter::for('login', function (Request $request) {
        return [
            Limit::perMinute(10)->by($request->ip()),
            Limit::perMinute(5)->by($request->input('email')),
        ];
    });
}
2

Aplica el middleware a las rutas

// routes/api.php
use Illuminate\Support\Facades\Route;

Route::middleware(['auth:sanctum', 'throttle:api'])->group(function () {
    Route::get('/user', [\App\Http\Controllers\UserController::class, 'show']);
    Route::get('/posts', [\App\Http\Controllers\PostController::class, 'index']);
});

Route::middleware(['auth:sanctum', 'throttle:uploads'])->group(function () {
    Route::post('/uploads', [\App\Http\Controllers\UploadController::class, 'store']);
});

Route::middleware(['throttle:login'])->group(function () {
    Route::post('/login', [\App\Http\Controllers\AuthController::class, 'login']);
});

Cabeceras de respuesta (X-RateLimit-*)

El middleware throttle añade automáticamente la información del límite en las cabeceras.
CabeceraDescripción
X-RateLimit-LimitNúmero de peticiones permitidas.
X-RateLimit-RemainingPeticiones restantes.
Retry-AfterSegundos hasta poder hacer la siguiente petición (solo en 429).
X-RateLimit-ResetTimestamp UNIX en el que se resetea el límite.
HTTP/1.1 429 Too Many Requests
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0
Retry-After: 45
X-RateLimit-Reset: 1717000000
Content-Type: application/json

{
    "message": "Too Many Requests."
}

Devolver una respuesta personalizada

RateLimiter::for('api', function (Request $request) {
    return Limit::perMinute(60)
        ->by($request->user()?->id ?: $request->ip())
        ->response(function (Request $request, array $headers) {
            return response()->json([
                'message' => 'Has superado el límite de peticiones. Inténtalo de nuevo en unos instantes.',
                'retry_after' => $headers['Retry-After'],
            ], 429, $headers);
        });
});

Comprobación manual con RateLimiter::attempt()

Si quieres comprobar el rate limit en cualquier punto del código sin usar el middleware throttle, utiliza RateLimiter::attempt().
use Illuminate\Support\Facades\RateLimiter;

class SmsController extends Controller
{
    public function send(Request $request): \Illuminate\Http\JsonResponse
    {
        $key = 'sms:' . $request->user()->id;

        $executed = RateLimiter::attempt(
            key: $key,
            maxAttempts: 5,
            callback: function () use ($request) {
                app(SmsService::class)->send(
                    $request->user()->phone,
                    $request->input('message')
                );
            },
            decaySeconds: 3600,  // 1 hora
        );

        if (! $executed) {
            $seconds = RateLimiter::availableIn($key);

            return response()->json([
                'message' => "Has superado el límite de envío de SMS. Reintenta en {$seconds} segundos.",
            ], 429);
        }

        return response()->json(['message' => 'SMS enviado.']);
    }
}

Comprobación y reset del contador

// Número actual de intentos
$hits = RateLimiter::attempts($key);

// Segundos hasta el siguiente reset
$seconds = RateLimiter::availableIn($key);

// Si se ha alcanzado el límite
$tooMany = RateLimiter::tooManyAttempts($key, $maxAttempts = 5);

// Resetear el contador manualmente (tras logout, etc.)
RateLimiter::clear($key);

Ejemplo de throttling de login

public function login(Request $request): mixed
{
    $key = 'login:' . $request->input('email');

    if (RateLimiter::tooManyAttempts($key, 5)) {
        $seconds = RateLimiter::availableIn($key);

        throw ValidationException::withMessages([
            'email' => "Demasiados intentos de login. Reintenta en {$seconds} segundos.",
        ]);
    }

    if (! Auth::attempt($request->only('email', 'password'))) {
        RateLimiter::hit($key, 300);  // Cuenta durante 5 minutos

        throw ValidationException::withMessages([
            'email' => 'El email o la contraseña no son correctos.',
        ]);
    }

    RateLimiter::clear($key);

    return redirect()->intended('/dashboard');
}

Rate limiting según la respuesta

Para contar solo respuestas específicas, usa after(). Ejemplo que solo cuenta las respuestas 404 para prevenir enumeración de recursos:
use Symfony\Component\HttpFoundation\Response;

RateLimiter::for('resource-lookup', function (Request $request) {
    return Limit::perMinute(10)
        ->by($request->user()?->id ?: $request->ip())
        ->after(function (Response $response) {
            return $response->getStatusCode() === 404;
        });
});

Rate limiting con Redis

Basta con cambiar el driver de caché por defecto a Redis para que el middleware throttle lo utilice también.

Configuración del driver Redis

// config/cache.php
'default' => env('CACHE_DRIVER', 'redis'),
# .env
CACHE_DRIVER=redis
REDIS_HOST=127.0.0.1
REDIS_PORT=6379

Usar throttleWithRedis

Para usar el middleware de throttling optimizado para Redis, llama a throttleWithRedis() en bootstrap/app.php.
use Illuminate\Foundation\Application;

return Application::configure(basePath: dirname(__DIR__))
    ->withRouting(
        web: __DIR__ . '/../routes/web.php',
        api: __DIR__ . '/../routes/api.php',
        apiPrefix: 'api',
    )
    ->withMiddleware(function (\Illuminate\Foundation\Configuration\Middleware $middleware): void {
        $middleware->throttleWithRedis();
    })
    ->create();
Con esto, el middleware throttle se mapea a la clase ThrottleRequestsWithRedis y usa operaciones atómicas de Redis para contar con precisión.
Al usar throttleWithRedis(), asegúrate de que Redis esté disponible. Si la conexión falla, podrías rechazar todas las peticiones.

Ventajas de usar Redis

  • Escalado horizontal — puedes compartir contadores entre varias instancias.
  • Alta precisión — evita race conditions con operaciones atómicas.
  • Gestión de TTL — usa el TTL nativo de Redis para eliminar contadores automáticamente.

Páginas relacionadas

Caché

Consulta la configuración y uso de los drivers de caché de Laravel, incluido Redis.
Última modificación el 13 de julio de 2026