Dépannage · Laravel

Le scheduler Laravel ne tourne pas, ou vous cherchez une alternative ?

Pourquoi le scheduler de Laravel s’arrête en silence — l’entrée cron manquante, le mauvais utilisateur, l’environnement et les tâches qui se chevauchent — et comment y remédier, ou le remplacer.

Le scheduler de Laravel est piloté par une seule entrée cron système. Si elle est incorrecte — ou l’environnement sous-jacent — toutes les tâches planifiées s’arrêtent en silence. Voici la checklist.

1. L’unique entrée cron doit exister

Laravel a besoin d’exactement une ligne de crontab appelant schedule:run chaque minute. Vérifiez qu’elle existe pour le bon utilisateur (généralement votre utilisateur de déploiement, pas root) :

crontab -l

Elle doit être :

* * * * * cd /var/www/app && php artisan schedule:run >> /dev/null 2>&1

Si elle manque, ajoutez-la avec crontab -e. Une erreur de déploiement courante est de l’ajouter en tant que root alors que l’app tourne en tant que www-data (ou inversement).

2. Utilisez le bon binaire PHP et un chemin absolu

Sous cron, php peut ne pas se résoudre, ou être la mauvaise version. Utilisez le chemin complet et celui du projet :

* * * * * cd /var/www/app && /usr/bin/php8.3 artisan schedule:run >> /dev/null 2>&1

3. L’environnement diffère de votre shell

Cron ne charge pas votre profil shell, donc tout ce que vous y définissez manque. Laravel lit .env, ce qui est bien — mais si vos tâches appellent d’autres binaires, donnez-leur un PATH explicite. Assurez-vous aussi que APP_ENV et la configuration des files/cache correspondent à la production.

4. Vous avez masqué la sortie et ne pouvez plus déboguer

>> /dev/null 2>&1 cache tout, y compris les erreurs. Journalisez-la temporairement :

* * * * * cd /var/www/app && php artisan schedule:run >> storage/logs/schedule.log 2>&1

Puis exécutez php artisan schedule:run à la main et lisez la sortie — la plupart des échecs (permissions, env manquant, connexion BD) apparaissent immédiatement.

5. Les tâches se chevauchent ou se bloquent

Une tâche longue qui tourne chaque minute peut s’accumuler. Utilisez les garde-fous de Laravel pour qu’une exécution lente ne bloque pas la suivante :

$schedule->command('reports:build')
    ->hourly()
    ->withoutOverlapping()
    ->onOneServer();

Le problème de fond : le scheduler peut s’arrêter et rester silencieux

Si schedule:run cesse de se déclencher — le serveur a redémarré, la crontab a été effacée au déploiement, PHP a été mis à jour — Laravel n’a aucun moyen de vous le dire. Vos rapports et e-mails en file s’arrêtent tout simplement.

Envoyez un ping heartbeat depuis une tâche planifiée pour savoir que le scheduler lui-même est vivant :

$schedule->call(function () {
    Http::timeout(10)->get('https://ping.steadycron.com/<votre-jeton-ping>');
})->everyFifteenMinutes();

Si ce ping manque, SteadyCron vous alerte — le scheduler est en panne, avant que vos utilisateurs ne s’en aperçoivent.

Vous cherchez une alternative au scheduler Laravel, tout simplement ?

Si vous en avez assez de corriger sans cesse le même type de problème — une crontab effacée, une migration de serveur qui fait disparaître la seule ligne dont tout dépend, un schedule:run qui se déclenche techniquement mais échoue en silence — vous n’êtes pas obligé de garder ce point de défaillance unique.

La plupart des entrées $schedule->command(...) et $schedule->call(...) peuvent devenir une route :

// routes/api.php
Route::post('/cron/build-reports', function () {
    Artisan::call('reports:build');
    return response()->json(['ok' => true]);
})->middleware('auth.cron-token');

SteadyCron appelle ensuite ce point de terminaison selon le planning — avec son propre réessai/backoff, son délai d’expiration et son journal d’exécution — au lieu de dépendre de l’existence d’une entrée cron système, du bon binaire PHP, ou du déclenchement effectif de schedule:run. Le planning vit désormais dans SteadyCron, survit intact aux migrations de serveur, et chaque exécution affiche son code de statut et sa réponse, pas seulement ce qui a atterri dans storage/logs.

C’est la même idée que celle développée sur la page alternative au cron Docker — sortir le déclencheur du processus censé simplement répondre aux requêtes.