Laravel Scheduler Monitoring

Start monitoring your Laravel scheduler free — 10 checks, no credit card.

The problem

Laravel's task scheduler runs from a single cron entry — `* * * * * php artisan schedule:run` — and that is exactly why scheduler failures are so easy to miss. If that one line stops running (a deploy that drops the crontab, a server reboot, a full disk), every scheduled command silently stops with it and nothing alerts you. Even when the scheduler is running, an individual `Schedule::command()` can throw, exit non-zero, hang past its window, or get silently skipped by `withoutOverlapping`. Laravel's built-in `emailOutputOnFailure` only fires when the command actually ran and failed; it can't tell you about a job that never started or a scheduler that died. The result is the classic blind spot: your nightly backup, queue prune, or data sync quietly stops happening and you only find out when something downstream breaks.

How Pakyas helps

Pakyas is execution-signal based: each scheduled task proves it ran by sending a signal, and Pakyas compares those signals against the task's expected schedule. Laravel ships first-class hooks for exactly this — `pingBefore()`, `thenPing()`, `pingOnSuccess()`, and `pingOnFailure()` — so you wire a Pakyas check into the scheduler definition itself, no wrapper scripts and no SSH-ing into the box to edit crontabs. When a command starts, finishes, or fails, the scheduler pings Pakyas, and Pakyas turns silence and failure into distinct, actionable states: Missing (the signal never arrived, so the task — or the whole scheduler — never ran), Late (it ran outside its expected window), Overrunning (it started but never reported completion), and Error (it explicitly signaled failure with a non-zero exit code). Because the check is defined in source control alongside the task, the monitoring travels with your deploy.

Set it up

  1. Confirm the scheduler cron entry is in place

    # Laravel needs one cron entry per server to drive schedule:run
    * * * * * cd /path-to-your-project && php artisan schedule:run >> /dev/null 2>&1

    This single line runs every minute and dispatches all your due tasks. If it ever stops, Pakyas catches the resulting silence as a Missing check (see the optional heartbeat snippet below).

  2. Create a check in Pakyas and copy its ping URL

    # Sign up at https://pakyas.com and create a check for your scheduled task.
    # You get a unique ping URL using the check's public_id (a UUID):
    #
    #   start:    https://ping.pakyas.com/{public_id}/start
    #   success:  https://ping.pakyas.com/{public_id}
    #   fail:     https://ping.pakyas.com/{public_id}/fail
    #
    # Replace {public_id} below with the UUID Pakyas gives you.

    GET or POST both work. The bare URL signals success; append /start when the task begins and /fail on a non-zero exit.

  3. Wire the ping into your scheduled task

    <?php
    
    use Illuminate\Support\Facades\Schedule;
    
    // Store the check's base URL in config/env so it isn't hard-coded.
    // Add your own key (e.g. PAKYAS_BACKUP_URL) to .env and config/services.php.
    $url = config('services.pakyas.backup_url'); // "https://ping.pakyas.com/{public_id}"
    
    Schedule::command('backup:run')
        ->dailyAt('02:00')
        ->withoutOverlapping()
        ->pingBefore($url . '/start')   // task is starting
        ->pingOnSuccess($url)           // exit code 0
        ->pingOnFailure($url . '/fail'); // non-zero exit code

    pingBefore / pingOnSuccess / pingOnFailure are built-in Laravel scheduler methods — no extra package required. Define this in routes/console.php (Laravel 11+) or your scheduler closure.

  4. Optional: monitor the scheduler heartbeat itself

    <?php
    
    use Illuminate\Support\Facades\Http;
    use Illuminate\Support\Facades\Schedule;
    
    // A separate Pakyas check that pings every minute proves schedule:run is alive.
    // Supply your own scheduler check URL via config/env (a second check).
    $schedulerUrl = config('services.pakyas.scheduler_url');
    
    Schedule::call(fn () => Http::get($schedulerUrl))
        ->name('pakyas-scheduler-heartbeat')
        ->everyMinute();

    Set this Pakyas check's expected period to 1 minute. If the crontab dies, the heartbeat stops and Pakyas marks it Missing — catching the failure mode emailOutputOnFailure can never see.

A worked example

Take a nightly database backup defined as an Artisan command, scheduled with `Schedule::command('backup:run')->dailyAt('02:00')->withoutOverlapping()`. Wire it to a Pakyas check with `->pingBefore($url . '/start')->pingOnSuccess($url)->pingOnFailure($url . '/fail')`. Now: when the backup starts at 02:00 it pings `/start`, so Pakyas knows it began; if it completes cleanly it pings the success URL and the check stays On Schedule; if `backup:run` exits non-zero (S3 credentials expired, disk full) it pings `/fail` and Pakyas marks it Error with the failure captured. And if 02:00 comes and goes with no `/start` at all — because the server rebooted and the `schedule:run` crontab never came back — the check goes Missing and you get alerted, instead of discovering days later that you have no backups.

Pricing

Pakyas is free for up to 10 checks ($0, no credit card) — enough to monitor the scheduler heartbeat plus your most important commands. Paid tiers add more checks and channels: Developer at $9/mo, Pro at $29/mo, and Business at $99/mo.

See the full breakdown on the pricing page.

New to the terminology? See the cron monitoring glossary for plain-language definitions of every job state, or explore everything Pakyas tracks on the features page.

Start monitoring your Laravel scheduler free — 10 checks, no credit card.

Execution-signal precision: know when a job is Missing, Late, Overrunning, or reports an Error — not just up or down.

Start monitoring free