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 and fully hosted with no monitor cap and no paid tier. 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.