Skip to content
Guide

How to monitor the Laravel scheduler and its tasks.

The Laravel scheduler fails without a sound when its one cron entry stops. How to monitor scheduled tasks and the scheduler itself with built-in ping hooks.

6 minute read · Updated

Laravel’s task scheduler is a pleasant way to run recurring work: you describe every task in code, and a single cron entry runs them all. That convenience hides a risk. If something goes wrong with the scheduler, none of your tasks run, and nothing in your application tells you. Reports stop going out, queues stop being pruned, subscriptions stop renewing, and the logs are silent because nothing ran to write to them.

This guide shows how to monitor Laravel scheduled tasks, and the scheduler itself, using the ping hooks Laravel already provides.

How the scheduler works, and where it breaks

The scheduler depends on one cron entry that runs every minute:

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

Each minute, schedule:run works out which tasks are due and runs them. Note the >> /dev/null 2>&1: the output is thrown away, so even when the scheduler fails there is nothing to read.

There are two separate layers that can fail:

  • The scheduler as a whole. The cron entry was lost in a server rebuild, the deployment user changed, the PHP path moved, the server is off, or the container that ran cron was replaced.
  • An individual task. It throws an exception, runs out of memory, times out, is skipped because withoutOverlapping still holds a lock from a crashed run, or is stopped because the application is in maintenance mode.

Good monitoring covers both, and they need different checks.

Monitoring individual tasks with ping hooks

Laravel can make an HTTP request around any scheduled task. You give each task its own heartbeat check and use these methods:

  • pingBefore($url) requests the address just before the task starts. Pingvella uses this as the start signal to time the run.
  • thenPing($url) requests the address after the task finishes. This is the check-in.
  • pingOnFailure($url) requests the address when the task fails. Add /fail to the check’s address to report the failure immediately.

Put together, a nightly task looks like this:

$schedule->command('reports:send') ->dailyAt('02:00') ->pingBefore('https://pingvella.com/ping/your-check-uuid/start') ->thenPing('https://pingvella.com/ping/your-check-uuid') ->pingOnFailure('https://pingvella.com/ping/your-check-uuid/fail');

If the task never starts, no start signal arrives. If it starts and fails, the failure is reported at once. If it hangs, neither the check-in nor the failure arrives, and the missing check-in raises the alert when the grace period ends.

Use the right method for the job

Laravel also has pingBeforeIf, thenPingIf and pingOnSuccess for conditional pings. A common choice is to use pingOnSuccess for the check-in, so it is only sent when the task succeeded, along with pingOnFailure for the failure signal. Both work with Pingvella.

Each check page in Pingvella shows this code already filled in with the check’s own address and schedule, so you can copy it straight into routes/console.php or your schedule definition.

Monitoring the scheduler itself

Task-level pings have a blind spot. If the scheduler is not running, no task runs, and so no task sends a signal. You do get an alert for each task once its grace period passes, but that can be a day later for a nightly job, and a failure of the scheduler is a different problem from a failure of a task.

The fix is a scheduler heartbeat: a task that does nothing except check in, every minute.

$schedule->call(fn () => null) ->everyMinute() ->thenPing('https://pingvella.com/ping/your-scheduler-check-uuid');

Create a check for it with a one-minute schedule and a short grace period of a few minutes. If the cron entry disappears, the server stops or the scheduler is stuck, the check-ins stop and you are alerted within minutes, not whenever the first nightly task is due. This one check is the single most valuable monitor on a Laravel server.

Things that quietly break Laravel scheduled tasks

  • Overlap locks. withoutOverlapping() skips a run while the previous one holds its lock. If a run crashes and the lock outlives it, the task can stay skipped until the lock expires. Set an explicit expiry.
  • Running on one server. onOneServer() relies on a shared cache. If the cache is unavailable, tasks may not run anywhere.
  • Background tasks. With runInBackground(), the parent process may report success before the task has actually finished. Ping at the end of the task itself if the outcome matters.
  • Maintenance mode. Scheduled commands are skipped while the app is down for maintenance unless you add evenInMaintenanceMode(). Pause the check for planned maintenance.
  • Time zones. A task scheduled with ->timezone() runs at a different clock time from the server’s. Match the check’s schedule to the task’s own time zone.
  • Queued work. A scheduled task that dispatches a job is “done” when the job is queued, not when it has run. Monitor the queue worker separately with its own heartbeat.

A sensible set of checks

  1. One heartbeat for the scheduler itself, every minute.
  2. One check for each task that matters: billing, backups, reports, syncs, cleanup.
  3. One heartbeat for the queue worker, if you run one.
  4. A website check for the application’s public address, since a healthy scheduler on a server whose site is down is only half a picture.

To see how heartbeats work without any framework in the way, read heartbeat monitoring explained, or how to monitor cron jobs for plain cron and shell scripts.

Get started free

Every Pingvella account includes three heartbeat checks free, which is enough for the scheduler itself and your two most important tasks. There is more on the cron job monitoring page.

Common questions

Does it work with the Laravel scheduler?

Yes. Each check page shows ready-made Laravel scheduler code on the check’s own schedule, using pingBefore, thenPing and pingOnFailure, plus a heartbeat that tells you the scheduler itself is running.

How do I know the Laravel scheduler itself is running?

Schedule a task that does nothing but check in every minute. If the scheduler’s cron entry is removed or the server stops, the check-ins stop and Pingvella alerts you, even though none of your other tasks gave any sign of trouble.

What if my job runs but fails?

Report it. Request your check’s /fail URL, or end the URL with the job’s exit status (/$? in a shell script), and Pingvella marks the check down straight away instead of waiting for the deadline to pass.

There are more answers on the FAQ page.

Start in a couple of minutes

Hear about it before your customers do.

Create a free account