Zum Hauptinhalt springen

Wie die Facade RateLimiter funktioniert

Das Rate Limiting in Laravel setzt sich aus der Klasse Illuminate\Cache\RateLimiting\Limit und der Facade RateLimiter zusammen. Intern speichert es Zähler im Cache-Treiber (standardmäßig File oder Redis) und verfolgt so die Anzahl der Anfragen. Die Middleware throttle führt für jede Anfrage die per RateLimiter::for() definierte Closure aus und liefert bei Überschreitung 429 Too Many Requests.

Definition eigener Limiter im AppServiceProvider

Die Rate-Limit-Konfiguration erfolgt in der boot()-Methode von 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());
        });
    }
}
Das erste Argument von RateLimiter::for() ist der Limiter-Name, den die Middleware throttle verwendet. Die Closure als zweites Argument muss eine Instanz von Illuminate\Cache\RateLimiting\Limit zurückgeben.

Rate Limits nach Nutzer, IP-Adresse oder Plan

Unterschiedliche Limits für authentifizierte Nutzer und Gäste

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

Limits nach Nutzerplan

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),
    };
});

Globales Limit je IP-Adresse

Throttling pro IP-Adresse, unabhängig vom Endpunkt.
RateLimiter::for('global', function (Request $request) {
    return Limit::perMinute(1000)->by($request->ip());
});

Mehrere Limits kombinieren

Ein Array führt dazu, dass alle Limits ausgewertet werden. Sobald eines erreicht wird, folgt 429.
RateLimiter::for('login', function (Request $request) {
    return [
        Limit::perMinute(10)->by($request->ip()),
        Limit::perMinute(5)->by($request->input('email')),
    ];
});
Wenn Sie mehrere Limits mit demselben by-Wert definieren, versehen Sie die Keys mit Präfixen, damit sie nicht kollidieren.
RateLimiter::for('uploads', function (Request $request) {
    return [
        Limit::perMinute(10)->by('minute:' . $request->user()->id),
        Limit::perDay(1000)->by('day:' . $request->user()->id),
    ];
});

Angabe eigener Limiter-Namen an der throttle-Middleware

Übergeben Sie den definierten Limiter-Namen an die Middleware throttle.
use Illuminate\Support\Facades\Route;

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

Registrierung in bootstrap/app.php

Ab Laravel 11 verwalten Sie Middleware in 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();

Beispielhafte Anwendung auf API-Routen

1

Limiter definieren

Definieren Sie im AppServiceProvider mehrere Limiter.
public function boot(): void
{
    // Allgemeiner API-Zugriff
    RateLimiter::for('api', function (Request $request) {
        return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip());
    });

    // Datei-Uploads
    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());
    });

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

Middleware an Routen anwenden

// 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']);
});

Response-Header (X-RateLimit-*)

Die Middleware throttle fügt Limit-Infos automatisch als Response-Header an.
HeaderBeschreibung
X-RateLimit-LimitErlaubte Anfragezahl
X-RateLimit-RemainingVerbleibende Anfragen
Retry-AfterSekunden bis zur nächsten möglichen Anfrage (nur bei 429)
X-RateLimit-ResetUNIX-Zeitstempel, an dem das Limit zurückgesetzt wird
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."
}

Eigene Response zurückgeben

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' => 'Anfragelimit überschritten. Bitte versuchen Sie es später erneut.',
                'retry_after' => $headers['Retry-After'],
            ], 429, $headers);
        });
});

Manuelle Prüfung mit RateLimiter::attempt()

Wenn Sie das Rate Limit unabhängig von der throttle-Middleware zu einem beliebigen Zeitpunkt im Code prüfen wollen, nutzen Sie 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 Stunde
        );

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

            return response()->json([
                'message' => "SMS-Sendelimit überschritten. Bitte in {$seconds} Sekunden erneut versuchen.",
            ], 429);
        }

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

Anzahl der Versuche prüfen und zurücksetzen

// Aktuelle Anzahl an Versuchen
$hits = RateLimiter::attempts($key);

// Sekunden bis zum nächsten Reset
$seconds = RateLimiter::availableIn($key);

// Ist das Limit erreicht?
$tooMany = RateLimiter::tooManyAttempts($key, $maxAttempts = 5);

// Zähler manuell zurücksetzen (z. B. nach Logout)
RateLimiter::clear($key);

Beispiel: Login-Throttling

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

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

        throw ValidationException::withMessages([
            'email' => "Zu viele Login-Versuche. Bitte in {$seconds} Sekunden erneut versuchen.",
        ]);
    }

    if (! Auth::attempt($request->only('email', 'password'))) {
        RateLimiter::hit($key, 300);  // 5 Minuten zählen

        throw ValidationException::withMessages([
            'email' => 'E-Mail-Adresse oder Passwort falsch.',
        ]);
    }

    RateLimiter::clear($key);

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

Response-basiertes Rate Limiting

Wenn nur bestimmte Responses gezählt werden sollen, verwenden Sie after(). Ein Beispiel, das nur 404-Responses zählt, um Enumeration-Angriffe zu verhindern:
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 mit Redis

Wenn Sie den Cache-Standardtreiber auf Redis umstellen, nutzt auch die throttle-Middleware automatisch Redis.

Redis-Treiber konfigurieren

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

throttleWithRedis verwenden

Für die Redis-optimierte Throttle-Middleware rufen Sie in bootstrap/app.php die Methode throttleWithRedis() auf.
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();
Dadurch wird die throttle-Middleware auf die Klasse ThrottleRequestsWithRedis gemappt und nutzt atomare Redis-Operationen für präzises Zählen.
Wenn Sie throttleWithRedis() verwenden, stellen Sie sicher, dass Redis stets verfügbar ist. Fällt die Verbindung zu Redis aus, können unter Umständen alle Anfragen abgewiesen werden.

Vorteile von Redis

  • Horizontal skalierbar — Zähler werden über mehrere Serverinstanzen geteilt.
  • Hohe Genauigkeit — atomare Operationen verhindern Race Conditions.
  • TTL-Verwaltung — die native Ablaufzeit von Redis löscht die Zähler automatisch.

Verwandte Seiten

Cache

Erfahren Sie mehr über Konfiguration und Verwendung der Laravel-Cache-Treiber inklusive Redis.
Zuletzt geändert am 13. Juli 2026