Monitor PHP cron jobs: Laravel scheduler callbacks or plain try/catch
In Laravel, attach onSuccess and onFailure
callbacks to any scheduled command — one HTTP call each. Without a
framework, a try/catch plus file_get_contents
is all you need: ping on success, POST the exception to
/fail on failure. Absence detection covers PHP fatal errors
and OOM kills that bypass every catch block. Free, no monitor cap.
The snippets
Create a monitor in the console, set the schedule plus a grace window, copy the ping URL, and add it to your script or scheduler config.
Plain PHP — file_get_contents, zero dependencies
file_get_contents with a stream context sends both
the GET success ping and the POST failure body. Re-throwing keeps
the exit code non-zero so your
cron entry still sees the failure:
Laravel scheduler — onSuccess / onFailure
In app/Console/Kernel.php, chain
onSuccess and onFailure onto any
scheduled command. The onFailure closure receives a
Throwable argument in Laravel 10+:
Laravel artisan command — inside the handle method
For commands that are invoked outside the scheduler (e.g. from
Jenkins or a deployment hook), add the try/catch directly inside
the handle() method. Return a non-zero exit code on
failure so the caller can detect it:
register_shutdown_function for fatal errors
PHP fatal errors and parse errors terminate the process before any catch block runs. A shutdown function can catch the last error, but absence-detection is still the safer net — if PHP can't even bootstrap, no shutdown function fires. The missing ping opens the incident regardless. See also the LastPing MCP server for AI-agent provisioning and the Node.js guide for a comparison pattern.
What this catches
The catch reports what it can; the silence reports the
rest.
- A thrown exception or error — the
/failping opens an incident immediately with the exception message stored on the ping event. - A PHP fatal error — the shutdown function catches the last error and POSTs it to
/failbefore the process exits. - OOM kill or SIGKILL — no ping fires; the missing ping opens the incident after the grace window.
- The script never ran — missing cron entry, dead server, broken include path. Absence → incident.
- Recovery — the next successful run closes the incident and sends a recovery notice automatically.
Questions people ask
Front-loaded answers — the most important fact first.
-
Do I need Composer or a package?
No.
file_get_contents()with a stream context handles both GET and POST without any dependencies. Guzzle or Laravel'sHttpfacade are convenient but optional. -
How does Laravel's onFailure know the exception?
In Laravel 10+ the
onFailureclosure signature accepts a\Throwableargument. Laravel injects it automatically if you declare it as a parameter. In earlier versions usewithoutOverlapping()combined with a custom command that returns a non-zero exit code. -
What catches a PHP fatal error or parse error?
Register a shutdown function with
register_shutdown_functionand checkerror_get_last(). For the most severe cases — OOM kills, SIGKILL — absence-detection is the real safety net: no ping reaches the server, and that silence opens the incident. -
Is this free?
Yes. LastPing is free for individuals with no monitor cap. See how it compares to Healthchecks.io and Cronitor.
Fatal errors don't reach your catch block — absence does.
One callback now beats a quiet ETL gap later. First alert wired up in about a minute — and LastPing monitors itself the same way.