Fehlerbehebung · Laravel
Laravel-Scheduler läuft nicht — oder suchen Sie eine Alternative?
Warum Laravels Scheduler still ausfällt — der fehlende Cron-Eintrag, falscher Benutzer, Umgebung und überlappende Tasks — und wie Sie es beheben oder ersetzen.
Laravels Scheduler wird von einem einzigen System-Cron-Eintrag angetrieben. Ist der falsch — oder die Umgebung darunter — fallen alle geplanten Tasks still aus. Hier die Checkliste.
1. Der eine Cron-Eintrag muss existieren
Laravel braucht genau eine Crontab-Zeile, die schedule:run jede Minute aufruft.
Stellen Sie sicher, dass sie für den richtigen Benutzer existiert (meist Ihr
Deploy-Benutzer, nicht root):
crontab -l
Sie sollte lauten:
* * * * * cd /var/www/app && php artisan schedule:run >> /dev/null 2>&1
Fehlt sie, fügen Sie sie mit crontab -e hinzu. Ein häufiger Deploy-Fehler ist,
sie als root hinzuzufügen, während die App als www-data läuft (oder umgekehrt).
2. Nutzen Sie das richtige PHP-Binary und absolute Pfade
Unter Cron lässt sich php womöglich nicht auflösen oder ist die falsche Version.
Nutzen Sie den vollen Pfad und den Projektpfad:
* * * * * cd /var/www/app && /usr/bin/php8.3 artisan schedule:run >> /dev/null 2>&1
3. Die Umgebung unterscheidet sich von Ihrer Shell
Cron lädt Ihr Shell-Profil nicht, also fehlt alles, was Sie dort setzen. Laravel
liest .env, was in Ordnung ist — aber wenn Ihre Tasks andere Binaries aufrufen,
geben Sie ihnen einen expliziten PATH. Stellen Sie außerdem sicher, dass APP_ENV
sowie Queue-/Cache-Konfiguration zur Produktion passen.
4. Sie haben die Ausgabe unterdrückt und können nun nicht debuggen
>> /dev/null 2>&1 verbirgt alles, auch Fehler. Loggen Sie es vorübergehend:
* * * * * cd /var/www/app && php artisan schedule:run >> storage/logs/schedule.log 2>&1
Führen Sie dann php artisan schedule:run von Hand aus und lesen Sie die Ausgabe —
die meisten Fehler (Berechtigungen, fehlende Env, DB-Verbindung) zeigen sich sofort.
5. Tasks überlappen oder hängen
Ein langer Task, der jede Minute läuft, kann sich stauen. Nutzen Sie Laravels Guards, damit ein langsamer Lauf den nächsten nicht blockiert:
$schedule->command('reports:build')
->hourly()
->withoutOverlapping()
->onOneServer();
Das tiefere Problem: Der Scheduler kann stoppen und still bleiben
Stoppt schedule:run — der Server wurde neu gestartet, die Crontab beim Deploy
gelöscht, PHP aktualisiert — hat Laravel keine Möglichkeit, es Ihnen zu sagen. Ihre
in Queues eingereihten Reports und E-Mails hören einfach auf.
Pingen Sie einen Heartbeat aus einem geplanten Task, damit Sie wissen, dass der Scheduler selbst lebt:
$schedule->call(function () {
Http::timeout(10)->get('https://ping.steadycron.com/<ihr-ping-token>');
})->everyFifteenMinutes();
Bleibt dieser Ping aus, alarmiert Sie SteadyCron — der Scheduler ist down, bevor Ihre Nutzer es merken.
Suchen Sie eine Laravel-Scheduler-Alternative ganz ohne Cron?
Wenn Sie es leid sind, immer wieder dieselbe Art von Problem zu beheben — eine
gelöschte Crontab, eine Server-Migration, die genau die eine Zeile verliert,
von der alles abhängt, ein schedule:run, das technisch feuert, aber still
einen Fehler wirft — müssen Sie diesen Single Point of Failure gar nicht erst
behalten.
Die meisten $schedule->command(...)- und $schedule->call(...)-Einträge
lassen sich als Route abbilden:
// routes/api.php
Route::post('/cron/build-reports', function () {
Artisan::call('reports:build');
return response()->json(['ok' => true]);
})->middleware('auth.cron-token');
SteadyCron ruft diesen Endpunkt dann planmäßig auf — mit eigenem
Retry/Backoff, Timeout und Ausführungsprotokoll — statt darauf zu vertrauen,
dass ein System-Cron-Eintrag existiert, das richtige PHP-Binary stimmt oder
schedule:run überhaupt aufgerufen wird. Der Zeitplan lebt jetzt in
SteadyCron, überlebt Server-Migrationen unverändert, und jeder Lauf zeigt
Statuscode und Antwort — nicht nur das, was es in storage/logs geschafft hat.
Dieselbe Idee wird ausführlicher auf der Seite Docker-Cron-Alternative behandelt — den Trigger aus dem Prozess herauszunehmen, der eigentlich nur Requests bedienen soll.