Laravel scheduler
Everything scheduled hangs off one crontab line, and Laravel has ping methods built in.
Everything in Schedule hangs off a single crontab line. If that line is lost in a server rebuild, or php moves after an upgrade, or the application is in maintenance mode, every scheduled task stops at once and Laravel has nowhere to report it from.
* * * * * cd /var/www/app && php artisan schedule:run >> /dev/null 2>&1
Laravel pings without a package
The scheduler has ping methods built in. They need Guzzle (composer require guzzlehttp/guzzle), which most applications already have.
// routes/console.php on Laravel 11 and 12
use Illuminate\Support\Facades\Schedule;
Schedule::command('backup:run')
->dailyAt('02:00')
->pingBefore(config('services.vivere.ping').'/start')
->pingOnSuccess(config('services.vivere.ping'))
->pingOnFailure(config('services.vivere.ping').'/fail');
On Laravel 10 and earlier the same calls go in the schedule() method of app/Console/Kernel.php, as $schedule->command(...).
Put the URL in config/services.php rather than calling env() directly, so it survives php artisan config:cache:
'vivere' => ['ping' => env('VIVERE_PING_URL')],
Closures and queued jobs
Schedule::call(function () {
Report::generate();
})->weeklyOn(1, '08:00')->thenPing(config('services.vivere.ping'));
Schedule::job(new SendDigest)->hourly()->thenPing(config('services.vivere.ping'));
thenPing on a queued job reports that the job was dispatched, not that it ran. If the queue worker is the part you are worried about, ping from inside the job's handle() instead.
Monitor settings
Use the task's schedule as a cron expression with the application timezone. Two Laravel behaviours to allow for in the grace: withoutOverlapping() silently skips a run while the previous one still holds the lock, and a task marked onOneServer() only runs where the cache lock is won, so the run can start on a machine with a slower disk than the one you timed. The calculator takes the worst normal duration and gives you a figure.