Monitor the Laravel scheduler
Laravel’s scheduler runs everything from one cron entry, so a single problem
can stop every task at once: the schedule:run cron line goes missing on a
new server, the queue worker dies, or one task starts throwing. A
heartbeat check per important task catches this:
the task reports to Pingstack after it succeeds, and Pingstack alerts you when
that report doesn’t arrive on time.
Laravel already knows how to send these reports. Its pingOnSuccess hook
calls a URL after a task finishes successfully, so this recipe needs no
extra packages.
1. Create a heartbeat check that matches the task
Section titled “1. Create a heartbeat check that matches the task”Find the task in routes/console.php (Laravel 11 and later) or
app/Console/Kernel.php (earlier versions):
Schedule::command('reports:send')->dailyAt('02:00');Create a heartbeat check and set its
schedule to Cron with the matching expression (0 2 * * * for
dailyAt('02:00')). Run php artisan schedule:list to see each task’s cron
expression and next run time.
Laravel runs tasks in your application’s timezone (APP_TIMEZONE, or
timezone in config/app.php) unless the task sets ->timezone(...). Choose
the same timezone in Pingstack. See
schedules and timezones.
Set a grace period longer than the task’s usual runtime, plus a margin. Attach a notification group.
2. Add the ping URL to your configuration
Section titled “2. Add the ping URL to your configuration”Copy the location’s ping URL from the check in Pingstack and add it to the
server’s .env, not to code:
PINGSTACK_REPORTS_PING_URL=Read it through a config file, because env() returns null outside config
files once you run php artisan config:cache. In config/services.php:
'pingstack' => [ 'reports_ping_url' => env('PINGSTACK_REPORTS_PING_URL'),],3. Ping on success
Section titled “3. Ping on success”Add pingOnSuccess to the task:
use Illuminate\Support\Facades\Schedule;
Schedule::command('reports:send') ->dailyAt('02:00') ->withoutOverlapping() ->onOneServer() ->pingOnSuccess(config('services.pingstack.reports_ping_url'));Laravel sends the request only when the command exits successfully, so a
failed run sends nothing and Pingstack alerts you when the deadline passes.
withoutOverlapping() stops a slow run from overlapping the next, and
onOneServer() keeps several app servers from each running and reporting the
task (it needs a shared cache such as Redis or the database).
On Laravel 10 and earlier, use the same chain on $schedule->command(...)
inside schedule() in app/Console/Kernel.php.
Don’t use thenPing for this. It pings after every run, including failed
ones, which would report a failing task as healthy.
4. Confirm the first report
Section titled “4. Confirm the first report”Run the task once through the scheduler’s test command:
php artisan schedule:test --name="reports:send"Open the check in Pingstack and confirm the location received a heartbeat and
shows a next deadline. If it didn’t, check that
config('services.pingstack.reports_ping_url') isn’t empty in
php artisan tinker.
Then confirm the scheduler itself runs on the server. The cron entry should look like this:
* * * * * cd /path-to-your-project && php artisan schedule:run >> /dev/null 2>&1If you use php artisan schedule:work under a process manager instead, check
that the process is running.
Optional: report a number
Section titled “Optional: report a number”pingOnSuccess only sends the URL. To send a count as a
numeric value, report from
the end of the command’s handle method instead, after the work succeeds:
use Illuminate\Support\Facades\Http;
Http::timeout(10)->retry(3, 1000)->get( config('services.pingstack.reports_ping_url'), ['value' => $sentCount],);Then remove pingOnSuccess from that task so it doesn’t report twice, and set
warning and critical thresholds on the check.
Several tasks
Section titled “Several tasks”Give each task you care about its own check and its own variable. If the whole scheduler stops, every check alerts, which tells you the cause is the scheduler rather than one task.
Why a healthy task might still alert
Section titled “Why a healthy task might still alert”- Timezone mismatch. The check and the app’s or task’s timezone must agree.
- Config cache. After adding the variable, run
php artisan config:cacheagain, or the URL is empty and no report is sent. - Background tasks. With
->runInBackground(), Laravel learns the result when the process finishes, so the report arrives after the work completes, not when the scheduler moves on. - Maintenance mode. Tasks don’t run in maintenance mode unless they call
->evenInMaintenanceMode().
If alerts still don’t match what you expect, see troubleshooting monitoring.