For Laravel

Your scheduler failed at 3 AM. When would you find out?

schedule:run exits 0 whether your backup ran or not. If the cron entry disappears, if the job hangs on a lock, if a deploy drops the task — there is no error page, no failed request, no log line you would ever look at. The absence of a thing is invisible.

CronAlive watches the scheduler from the outside and tells you when a run stops arriving. One method call per task, and the schedule comes from your code.

Start free — 10 checks Read the long version

What Laravel gives you, and where it stops

Laravel is honest about the problem — the scheduler has hooks for it:

$schedule->command('backup:run')
    ->daily()
    ->onFailure(fn () => /* notify someone */)
    ->thenPing('https://example.com/ping/backup');

Two limitations, though.

onFailure only fires when the task runs and fails. It cannot tell you about the task that never started: crontab wiped by a server migration, a supervisor that stopped restarting, a deploy that dropped routes/console.php changes. The most dangerous failure mode is the one where your code never executes — and in-process hooks can't see it by definition.

thenPing solves that, but it is manual. It is a dead man's switch: an external service alerts you when pings stop arriving. You create a check in some dashboard, copy its URL, paste it into the code, and repeat for every task. Twenty tasks, twenty copy-pastes. Then someone changes daily() to twiceDaily() and forgets to update the dashboard — now you get false alerts, or worse, none.

One line per task, schedule included

composer require cronalive/laravel

Two env vars:

CRONALIVE_PING_DOMAIN=https://ping.cronalive.com
CRONALIVE_PING_KEY=<your project ping key>

And then, per task, one method call — no dashboard round-trip:

$schedule->command('backup:run')
    ->dailyAt('03:30')
    ->pingCronaliveSlug('backup', graceSec: 1800, tags: ['backups', 'prod']);

On the first ping the check creates itself, and — this is the part that matters — it reads the schedule off your scheduled task. The schedule lives in your code and only in your code; the monitoring cannot drift out of sync with it, because it was never retyped anywhere.

What travels with the first ping
Your task What the check gets
->everyFiveMinutes() cron=*/5 * * * *
->hourly() cron=0 * * * *
->dailyAt('03:30') cron=30 3 * * *
->dailyAt('03:30')->timezone('Europe/Berlin') the same plus tz=Europe/Berlin
->everyThirtySeconds() period=60 — see the caveats
pingCronaliveSlug('etl', graceSec: 1800) grace=1800
pingCronaliveSlug('etl', tags: ['etl', 'prod']) tags=etl,prod

The timezone is the task's own if it has one, otherwise your app.timezone. Without graceSec the project default applies. Prefer addressing an existing check by id? Use pingCronalive('<uuid>').

Three things you get for free

Hung jobs

The macros send /start before the job. A start with no finish is a job stuck on a lock or killed by OOM — invisible to any in-process hook.

Non-zero exits

The success ping goes out only when the job exits 0. A failed job sends /fail and alerts immediately, without waiting out the grace period.

Run duration

A chart per job, so you notice the backup that quietly went from 4 minutes to 40 — before it becomes the backup that overlaps its next run.

Pings never hold your schedule

The macros do not use Laravel's pingBefore / thenPing / pingOnFailure. Those callbacks swallow errors, but they wait on Laravel's default scheduler client — 10 s to connect, 30 s in total, per signal, so up to 90 s for a job with three of them. Scheduler callbacks run inline, one after another, so an unreachable ping domain does not just delay one job — it shifts everything scheduled behind it.

Every signal is sent with connectTimeout(2) and timeout(5) instead, and a failure is swallowed: the signal either goes out fast or is quietly dropped. A dropped signal is still reported through your exception handler, but at most once per schedule:run — a dead domain would otherwise write the same trace for every signal of every job.

Monitoring must not break the thing it monitors. That is the whole rule the package is built on.

Caveats worth knowing before you wire it up

Creating is not updating. Schedule parameters apply only when the check is first created. After that, pings just count — a deploy can never silently rewrite a schedule someone tuned in the dashboard. Changed the schedule in code? Update the check in the dashboard, or delete it and let the next ping recreate it.

->when() / ->skip() filters stop the pings too, so a skipped run looks exactly like a missed one. Pause the check for expected skip windows, or move the condition inside the job.

Sub-minute tasks (everyThirtySeconds() and friends) are monitored as "pings at least once a minute": Laravel keeps the real interval outside the cron expression, and CronAlive's shortest period is 60 seconds. True for such a job, and it never false-alarms.

Grace has a floor of 30 seconds. Schedulers start jobs with a few seconds of jitter; a tighter grace would turn that jitter into false alerts, so anything lower is raised to 30 rather than refused.

The macros are runtime macros. They are attached to Illuminate\Console\Scheduling\Event on boot, so your IDE will flag them as "method not found". The package ships _ide_helper.php with the signatures — point your IDE at it once.

Where heartbeats stop

A green check proves the job ran — not that its outcome is real. A certificate renewal that ran but whose reload quietly failed leaves you with a happy scheduler and a site that stops loading for everyone in three weeks. Same shape as the original problem, one level up.

That question is answered from the outside: an HTTP check against the public URL, run from several regions, watching the status code, the response time and the expiry date of the certificate actually on the wire. Heartbeats watch the work, HTTP checks watch the result — both on the same dashboard, with the same alert channels.

Questions

Why is onFailure() not enough?

onFailure fires only when the task runs and fails. It cannot report the task that never started: a crontab wiped by a server migration, a supervisor that stopped restarting, a deploy that dropped the schedule changes. The most dangerous failure is the one where your code never executes, and an in-process hook cannot see it by definition.

How is this different from thenPing()?

thenPing is the same dead man's switch, wired by hand: you create a check in a dashboard, copy its URL into the code, and repeat per task. The schedule then lives in two places and drifts. pingCronaliveSlug creates the check on the first ping and reads the schedule off the scheduled task itself, so there is nothing to retype and nothing to desynchronise.

Can a ping slow down or break my scheduler?

No. Every signal is sent with a 2-second connect timeout and a 5-second total timeout, and a failure is swallowed: the signal either goes out fast or is quietly dropped. The macros deliberately avoid Laravel's pingBefore/thenPing callbacks, which wait on the scheduler's default client — up to 90 seconds for a job with three signals, inline, delaying everything scheduled behind it.

What happens if I change a task's schedule in code?

Nothing, on purpose. Schedule parameters apply only when the check is created; after that pings just count, so a deploy can never rewrite a schedule someone tuned in the dashboard. Update the check in the dashboard or via the API, or delete it and let the next ping recreate it.

Which Laravel versions are supported?

Laravel 10, 11, 12 and 13, on PHP 8.1+. Every major is covered by the same integration suite in CI — scheduler macros, signal timeouts and the swallowing of a dead ping domain are asserted against a real framework of each version, not against mocks.

What does it cost?

The free plan covers ten checks with no card, which is enough to wire up the jobs you actually worry about. The SDK itself is MIT-licensed and open.

Not using the scheduler for everything?

The same check works with a plain crontab line — see cron job monitoring — and if you just want to see when an expression actually fires, the cron expression tester is free and needs no account.

Weighing this up against something else? The comparisons with Healthchecks.io and Cronitor have the price arithmetic written out, and a section on where each of them beats us.

Wire up the jobs you actually worry about

Ten checks on the free plan, no card. One composer require and one method call per task — the checks appear by themselves on the next scheduler tick.

Create an account