Troubleshooting · Laravel
Laravel scheduler not running? Fix the silent failure
Why Laravel's scheduler silently stops — the missing cron entry, wrong user, env, and overlapping tasks — and how to fix it.
Laravel’s scheduler is driven by a single system cron entry. If that entry is wrong — or the environment under it is — every scheduled task silently stops. Here’s the checklist.
1. The one cron entry must exist
Laravel needs exactly one crontab line calling schedule:run every minute. Confirm
it’s there for the correct user (usually your deploy user, not root):
crontab -l
It should read:
* * * * * cd /var/www/app && php artisan schedule:run >> /dev/null 2>&1
If it’s missing, add it with crontab -e. A common deploy mistake is adding it as
root while the app runs as www-data (or vice versa).
2. Use the right PHP binary and absolute path
Under cron, php may not resolve, or may be the wrong version. Use the full path
and the project’s path:
* * * * * cd /var/www/app && /usr/bin/php8.3 artisan schedule:run >> /dev/null 2>&1
3. The environment differs from your shell
Cron doesn’t load your shell profile, so anything you set there is missing.
Laravel reads .env, which is fine — but if your tasks shell out to other
binaries, give them an explicit PATH. Also make sure APP_ENV and queue/cache
config match production.
4. You silenced the output and now can’t debug
>> /dev/null 2>&1 hides everything, including errors. Temporarily log it:
* * * * * cd /var/www/app && php artisan schedule:run >> storage/logs/schedule.log 2>&1
Then run php artisan schedule:run by hand and read the output — most failures
(permissions, missing env, DB connection) show up immediately.
5. Tasks overlap or hang
A long task that runs every minute can pile up. Use Laravel’s guards so a slow run doesn’t block the next:
$schedule->command('reports:build')
->hourly()
->withoutOverlapping()
->onOneServer();
The deeper problem: the scheduler can stop and stay quiet
If schedule:run stops firing — the server rebooted, the crontab was wiped on
deploy, PHP was upgraded — Laravel has no way to tell you. Your queued reports and
emails just stop.
Ping a heartbeat from a scheduled task so you know the scheduler itself is alive:
$schedule->call(function () {
Http::timeout(10)->get('https://ping.steadycron.com/<your-ping-token>');
})->everyFifteenMinutes();
If that ping goes missing, SteadyCron alerts you — the scheduler is down, before your users notice.