Zum Hauptinhalt springen

Was sind Queues

In Web-Anwendungen gibt es Vorgänge – z. B. E-Mail-Versand, Bildskalierung oder Aufrufe externer APIs – die mehrere Sekunden dauern können. Erledigen Sie diese synchron innerhalb eines HTTP-Requests, muss der Nutzer bis zur Antwort warten. Mit Laravels Queues erledigen Sie solche schweren Aufgaben asynchron im Hintergrund. Der Request liefert sofort die Antwort, die eigentliche Arbeit übernimmt ein Worker-Prozess.
Queues unterstützen mehrere Backends wie Datenbank, Redis oder Amazon SQS. In der Entwicklung führt der Treiber sync Jobs direkt aus, ohne eine echte Queue zu nutzen.

Queue-Konfiguration

config/queue.php

Die Queue-Konfiguration liegt in config/queue.php. Über die Umgebungsvariable QUEUE_CONNECTION wählen Sie den Treiber.
// config/queue.php
'default' => env('QUEUE_CONNECTION', 'database'),

.env-Konfiguration

# Treiber wählen
QUEUE_CONNECTION=database

# Für Redis
# QUEUE_CONNECTION=redis
# REDIS_HOST=127.0.0.1
# REDIS_PORT=6379

Vorbereitung des Datenbank-Treibers

Für den database-Treiber braucht es eine Tabelle. In neuen Laravel-11-Projekten ist die Migration bereits enthalten – andernfalls erzeugen Sie sie so:
php artisan make:queue-table
php artisan migrate

Vorbereitung des Redis-Treibers

Für den redis-Treiber ergänzen Sie in config/database.php die Redis-Verbindung und installieren den Client.
composer require predis/predis

SQS Overflow Storage

Amazon SQS begrenzt die Größe der Nachrichten-Payload. Für Jobs mit großen Payloads speichern Sie den Überschuss in einem Cache-Store und schicken an SQS nur einen Pointer.
'sqs' => [
    // ...
    'overflow' => [
        'enabled' => env('SQS_OVERFLOW_ENABLED', false),
        'store' => env('SQS_OVERFLOW_STORE'),
        'always' => false,
        'delete_after_processing' => true,
        'flush_on_clear' => env('SQS_OVERFLOW_FLUSH_ON_CLEAR', false),
    ],
],
  • Mit enabled werden Payloads ab 1 MB in den angegebenen Cache-Store ausgelagert.
  • Mit always: true werden alle SQS-Payloads unabhängig von der Größe im Cache-Store abgelegt.
  • delete_after_processing entfernt gespeicherte Payloads nach erfolgreicher Verarbeitung (Standard: true).
  • flush_on_clear löscht bei queue:clear den Overflow-Store. Nutzen Sie hier einen dedizierten Store, damit nicht der normale Cache mitgeleert wird.

Job-Klassen erstellen

make:job

Erzeugen Sie ein Grundgerüst mit dem Artisan-Befehl make:job.
php artisan make:job SendWelcomeEmail
Dies erzeugt app/Jobs/SendWelcomeEmail.php.

Aufbau einer Job-Klasse

<?php

namespace App\Jobs;

use App\Models\User;
use App\Mail\WelcomeMail;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Queue\Queueable;
use Illuminate\Support\Facades\Mail;

class SendWelcomeEmail implements ShouldQueue
{
    use Queueable;

    /**
     * Erzeugt eine neue Job-Instanz.
     */
    public function __construct(
        public User $user,
    ) {}

    /**
     * Führt den Job aus.
     */
    public function handle(): void
    {
        Mail::to($this->user->email)->send(new WelcomeMail($this->user));
    }
}
Das Interface ShouldQueue signalisiert Laravel, dass der Job in einer Queue asynchron verarbeitet werden soll. Das Trait Queueable liefert die für Queue-Operationen benötigten Methoden.
Übergeben Sie dem Konstruktor ein Eloquent-Modell, serialisiert Laravel automatisch nur die ID. Bei der Ausführung werden die Daten frisch aus der Datenbank geladen – die Payload bleibt schlank.

Jobs dispatchen

dispatch()

Aus Controllern oder Services stellen Sie einen Job mit dispatch() in die Queue.
use App\Jobs\SendWelcomeEmail;

// In einer Route oder einem Controller
public function register(Request $request): RedirectResponse
{
    $user = User::create($request->validated());

    // Job in die Queue stellen
    SendWelcomeEmail::dispatch($user);

    return redirect('/dashboard');
}

Verzögerter Dispatch

Mit delay() verschieben Sie die Ausführung.
// In 5 Minuten ausführen
SendWelcomeEmail::dispatch($user)->delay(now()->addMinutes(5));

dispatchAfterResponse()

Mit dispatchAfterResponse() läuft der Job unmittelbar nach dem Ausliefern der HTTP-Antwort. Es funktioniert auch mit dem sync-Treiber und eignet sich daher für kleine Aufgaben, die keinen eigenen Worker brauchen.
SendWelcomeEmail::dispatchAfterResponse($user);

Dispatch in eine bestimmte Queue

SendWelcomeEmail::dispatch($user)->onQueue('emails');

Queue Routing

Um bestimmte Job-Klassen standardmäßig einer bestimmten Verbindung oder Queue zuzuordnen, verwenden Sie in der boot()-Methode eines Service-Providers Queue::route(). So verwalten Sie Zuordnungen zentral, ohne in jeder Job-Klasse onQueue() / onConnection() zu schreiben.
use App\Concerns\RequiresVideo;
use App\Jobs\ProcessPodcast;
use App\Jobs\ProcessVideo;
use Illuminate\Support\Facades\Queue;

public function boot(): void
{
    Queue::route(ProcessPodcast::class, connection: 'redis', queue: 'podcasts');
    Queue::route(RequiresVideo::class, queue: 'video');
}
Sie können auch Interfaces, Traits oder Elternklassen angeben – die Regel gilt dann für alle Jobs, die diese implementieren, verwenden oder erweitern. Mehrere Jobs auf einmal:
Queue::route([
    ProcessPodcast::class => ['podcasts', 'redis'], // queue und connection
    ProcessVideo::class => 'videos',                // nur queue (Standardverbindung)
]);
Queue Routing lässt sich vom Job aus über onQueue() / onConnection() überschreiben.

Synchrone Ausführung (Test/Entwicklung)

dispatchSync() führt den Job unmittelbar aus, ohne die Queue zu benutzen.
SendWelcomeEmail::dispatchSync($user);

Bulk-Dispatch

Wenn Sie viele unabhängige Jobs auf einmal dispatchen möchten, verwenden Sie die Methode bulk() der Fassade Bus. Ideal, wenn Sie keine Batch-Nachverfolgung oder Callbacks benötigen. Bus::bulk() gruppiert die Jobs nach Verbindung und Queue-Name und pusht jede Gruppe gesammelt – das ist effizient.
use App\Jobs\ProcessUser;
use Illuminate\Support\Facades\Bus;

Bus::bulk(
    $users->map(fn ($user) => new ProcessUser($user))
);
Bus::bulk() schickt Jobs gesammelt als Batch in die Queue. Anders als bei Batch-Verarbeitung (Bus::batch()) gibt es kein Fortschritts-Tracking und keine Completion-Callbacks. Ideal, um viele unabhängige Jobs einfach in großem Volumen zu senden.

Jobs verarbeiten

queue:work

Starten Sie einen Queue-Worker, um Jobs abzuarbeiten.
php artisan queue:work
Sie können Treiber oder Queue gezielt angeben.
# Nur die Queue "emails" auf Redis
php artisan queue:work redis --queue=emails

# database-Treiber
php artisan queue:work database
queue:work läuft dauerhaft. Nach Codeänderungen starten Sie die Worker mit queue:restart neu. In Produktion managen Sie sie typischerweise mit einem Prozessmanager wie Supervisor.

Optionen zur Worker-Steuerung

Häufig genutzte Optionen können Sie kombinieren.
php artisan queue:work --tries=3 --timeout=60 --sleep=3
OptionBeschreibung
--tries=NMaximale Versuche eines Jobs. Danach gilt der Job als fehlgeschlagen
--timeout=NMaximaldauer eines Jobs in Sekunden. Bei Überschreitung wird der Worker abgebrochen
--sleep=NWartezeit in Sekunden bis zur nächsten Poll-Anfrage, wenn die Queue leer ist (Standard: 3)
--max-jobs=NBeendet den Worker nach N verarbeiteten Jobs
--max-time=NBeendet den Worker nach N Sekunden
--queue=A,BBearbeitet die Queues mit Priorisierung (A zuerst)

Retry-Einstellungen in der Job-Klasse

Statt Kommandozeilenoptionen kann es übersichtlicher sein, die Einstellungen direkt in der Job-Klasse zu definieren.
use Illuminate\Foundation\Queue\Queueable;
use Illuminate\Queue\Attributes\Tries;
use Illuminate\Queue\Attributes\Timeout;

#[Tries(3)]
#[Timeout(60)]
class SendWelcomeEmail implements ShouldQueue
{
    use Queueable;

    // ...
}

Job wieder freigeben (Release-Middleware)

Wenn ein Job unter bestimmten Bedingungen nicht ausgeführt, sondern zurück in die Queue gelegt werden soll, verwenden Sie die Release-Middleware.
use Illuminate\Queue\Middleware\Release;

/**
 * Middleware zurückgeben, die der Job durchläuft.
 */
public function middleware(): array
{
    return [
        // Wenn $condition true ist, in 60 Sekunden erneut freigeben
        Release::when($this->order->isPending(), releaseAfter: 60),
    ];
}
Release::unless() gibt frei, wenn die Bedingung false ist.
return [
    // Solange die Bestellung nicht bezahlt ist, in 60 Sekunden erneut freigeben
    Release::unless($this->order->isPaid(), releaseAfter: 60),
];
Für komplexere Bedingungen übergeben Sie eine Closure.
return [
    Release::when(function (): bool {
        return ! $this->order->isPaid();
    }, releaseAfter: 60),
];
Ein Release erhöht den Versuchszähler des Jobs. Setzen Sie #[Tries] bzw. $tries passend.

Fehlgeschlagene Jobs

Tabelle failed_jobs einrichten

Überschreitet ein Job die maximale Anzahl an Versuchen, landet er in der Tabelle failed_jobs. Falls die Tabelle fehlt:
php artisan make:queue-failed-table
php artisan migrate

Aufräumen im Fehlerfall

Definieren Sie in der Job-Klasse die Methode failed(), um im Fehlerfall aufzuräumen.
use Throwable;

public function failed(?Throwable $exception): void
{
    // z. B. eine Slack-Benachrichtigung an Admins senden
    // Notification::route('slack', config('app.slack_webhook'))
    //     ->notify(new JobFailedNotification($this, $exception));
}

Retries für bestimmte Exceptions unterbinden

Manchmal möchte man bei bestimmten Exceptions gar nicht erst retryen. In bootstrap/app.php innerhalb von withExceptions() verwenden Sie dontRetry.
use App\Exceptions\InvalidPodcastSourceException;
use Illuminate\Foundation\Configuration\Exceptions;

->withExceptions(function (Exceptions $exceptions): void {
    $exceptions->dontRetry([
        InvalidPodcastSourceException::class,
    ]);
})
Für feinere Kontrolle nutzen Sie dontRetryWhen mit einer Closure. Gibt sie true zurück, wird der Job sofort als fehlgeschlagen markiert und nicht mehr retryet.
use App\Exceptions\PodcastProcessingException;
use Illuminate\Foundation\Configuration\Exceptions;

->withExceptions(function (Exceptions $exceptions): void {
    $exceptions->dontRetryWhen(function (PodcastProcessingException $e) {
        return $e->reason() === 'Subscription expired';
    });
})
Bei Fehlern, deren Ergebnis sich durch Retries nicht ändert – etwa Validierungsfehler oder eine abgelaufene Zahlungs-Subscription – ist ein sofortiges Fehlschlagen effizienter.

Liste fehlgeschlagener Jobs

php artisan queue:failed

Fehlgeschlagene Jobs erneut ausführen

# Bestimmte Job-ID retryen
php artisan queue:retry ce7bb17c-cdd8-41f0-a8ec-7b4fef4e5ece

# Alle retryen
php artisan queue:retry all

Fehlgeschlagene Jobs löschen

# Einzelnen Job löschen
php artisan queue:forget ce7bb17c-cdd8-41f0-a8ec-7b4fef4e5ece

# Alle löschen
php artisan queue:flush

Häufig verwendete Queue-Treiber

database-Treiber

Ein einfacher Treiber, ohne zusätzliche Infrastruktur direkt einsetzbar. Jobs landen in der Tabelle jobs, Worker pollen diese und verarbeiten sie.
  • Vorteile: einfache Einrichtung, nutzt Ihre vorhandene RDBMS
  • Nachteile: erhöht die DB-Last – für sehr viele Jobs weniger geeignet
QUEUE_CONNECTION=database

redis-Treiber

Der in Produktion am häufigsten verwendete, schnelle Treiber. Da Redis im Speicher arbeitet, sind Durchsatz und Skalierung deutlich besser.
  • Vorteile: schnell, skalierbar
  • Nachteile: Redis-Server erforderlich
QUEUE_CONNECTION=redis
REDIS_HOST=127.0.0.1
REDIS_PORT=6379
Für Redis-Queues in Produktion sollten Sie Laravel Horizon in Betracht ziehen. Es bietet ein schönes Dashboard mit Echtzeit-Überblick über die Jobs.

Produktivbetrieb mit Supervisor

In Produktion sollten queue:work-Prozesse bei einem Ausfall automatisch neu starten. Unter Linux ist Supervisor der Klassiker.
# /etc/supervisor/conf.d/laravel-worker.conf
[program:laravel-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/your-app/artisan queue:work --sleep=3 --tries=3 --max-time=3600
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
user=www-data
numprocs=2
redirect_stderr=true
stdout_logfile=/var/www/your-app/storage/logs/worker.log
stopwaitsecs=3600
Mit numprocs=2 starten Sie zwei Worker parallel. Danach Supervisor neu einlesen:
sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl start laravel-worker:*

Beispiel: E-Mail-Versand via Queue

1

Job-Klasse erstellen

php artisan make:job SendOrderConfirmation
2

Job-Logik implementieren

<?php

namespace App\Jobs;

use App\Models\Order;
use App\Mail\OrderConfirmed;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Queue\Queueable;
use Illuminate\Queue\Attributes\Tries;
use Illuminate\Queue\Attributes\Timeout;
use Illuminate\Support\Facades\Mail;

#[Tries(3)]
#[Timeout(30)]
class SendOrderConfirmation implements ShouldQueue
{
    use Queueable;

    public function __construct(
        public Order $order,
    ) {}

    public function handle(): void
    {
        Mail::to($this->order->user->email)
            ->send(new OrderConfirmed($this->order));
    }
}
3

Aus dem Controller dispatchen

use App\Jobs\SendOrderConfirmation;

public function store(Request $request): RedirectResponse
{
    $order = Order::create($request->validated());

    SendOrderConfirmation::dispatch($order);

    return redirect()->route('orders.show', $order)
        ->with('success', 'Bestellung angenommen.');
}
4

Worker starten

php artisan queue:work --tries=3 --timeout=30

Zusammenfassung

  • E-Mail-/SMS-Versand
  • Bilder-/Videokonvertierung
  • Aufrufe externer APIs
  • Report- oder CSV-Erstellung
  • Webhook-Versand
Setzen Sie in der .env QUEUE_CONNECTION=sync, werden Jobs direkt ausgeführt. So funktioniert alles auch ohne Worker – ideal beim Entwickeln.
QUEUE_CONNECTION=sync
# Worker starten
php artisan queue:work

# Worker neu starten (nach Deployment)
php artisan queue:restart

# Liste fehlgeschlagener Jobs
php artisan queue:failed

# Alle fehlgeschlagenen Jobs erneut ausführen
php artisan queue:retry all

# Alle fehlgeschlagenen Jobs löschen
php artisan queue:flush
Zuletzt geändert am 13. Juli 2026