Monitoring the Laravel task scheduler

Updated November 2, 2026

Laravel’s scheduler keeps all your periodic tasks in code, behind a single crontab line. That single line is also the weak spot: if it’s missing on a new server, nothing scheduled runs and Laravel has no way to tell you. The good news is that the scheduler can ping a monitoring URL by itself, so watching it takes a few lines.

Ping from each task

Since Laravel 11, schedules live in routes/console.php:

use Illuminate\Support\Facades\Schedule;

Schedule::command('backup:run')
    ->dailyAt('03:17')
    ->withoutOverlapping()
    ->pingBefore('https://ping.heartcron.com/<uuid>/start')
    ->pingOnSuccess('https://ping.heartcron.com/<uuid>')
    ->pingOnFailure('https://ping.heartcron.com/<uuid>/fail');

In Laravel 10 and earlier the same chain goes in the schedule() method of app/Console/Kernel.php, starting with $schedule->command(...).

Keep URLs out of the code and ping only from production:

// config/services.php
'heartcron' => [
    'backup' => env('HEARTCRON_BACKUP_URL'),
],

// routes/console.php
$url = config('services.heartcron.backup');

Schedule::command('backup:run')
    ->dailyAt('03:17')
    ->environments(['production'])
    ->pingBefore("$url/start")
    ->pingOnSuccess($url)
    ->pingOnFailure("$url/fail");

Watch the scheduler itself

Every scheduled task depends on this crontab entry:

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

If it goes missing, the per-task checks will eventually fire, but a weekly task would stay silent for a week. Add a heartbeat that runs every five minutes and does nothing but ping:

Schedule::call(fn () => null)
    ->everyFiveMinutes()
    ->name('heartbeat')
    ->thenPing(config('services.heartcron.scheduler'));

Give it a check with a 5-minute period and a 5-minute grace period. When the crontab line disappears or the server stops, you know within ten minutes.

Several servers

If the app runs on more than one server, add ->onOneServer() to tasks that must run once. It needs a cache driver shared by all servers, such as Redis or a database. The ping then comes from whichever server ran the task, and the check still sees exactly one run.

In containers

Instead of cron, run php artisan schedule:work as the main process of a separate container. It runs the scheduler every minute in the foreground. The heartbeat task above tells you when that container stops.

More guides