Skip to main content
All posts
· 5 min read

Laravel Cron vs Queue Monitoring — What's the Difference?

Generic uptime monitors miss scheduler failures. Queue depth monitors miss Horizon supervisor death. Here's why you need both, and what each one actually covers.

If you’ve set up cron monitoring for your Laravel app, you might think you have queue monitoring covered too. You don’t. And if you’ve set up Horizon alerting, you might think your scheduled tasks are covered. They’re not.

These are different systems with different failure modes, and monitoring one doesn’t tell you anything about the other.

What a cron monitor actually watches

A generic cron monitor sends a ping URL to a service on each scheduler run:

* * * * * cd /app && php artisan schedule:run && curl -s https://monitor.example.com/ping/abc123

This tells you: the schedule:run command ran and exited cleanly.

It doesn’t tell you:

  • Whether any scheduled task actually ran (the scheduler may have had no tasks due)
  • Whether a specific task succeeded or failed
  • What exit code a command returned
  • Whether a task was skipped due to withoutOverlapping()

A ping-based monitor gives you one bit of information: the scheduler process didn’t crash. That’s useful but incomplete. You can have a green ping monitor while every scheduled task in your app has been failing for days.

What Horizon monitoring actually watches

Horizon monitoring typically watches the supervisor status — is Horizon running, paused, or stopped? Some tools also watch queue depth.

This tells you: the queue orchestration layer is up and jobs aren’t accumulating abnormally.

It doesn’t tell you:

  • Whether any specific scheduled command ran
  • Whether the scheduler process is alive
  • Whether a command that dispatches jobs is actually running
  • What happened to any specific run in history

A task like SendWeeklyReports might dispatch jobs successfully, but if the scheduler never ran, no jobs were dispatched. Horizon looks healthy because no new jobs arrived, not because everything is fine.

The failure modes each misses

Ping monitor passes, but a specific task is failing:

schedule:run → runs ✓
SendInvoices → throws exception ✗
ping fires ✓

The cron monitor shows green. The invoices job failed silently.

Horizon shows running, but the scheduler is dead:

php artisan schedule:run → not running (missing cron entry after deploy)
Horizon → healthy (workers are up, but nothing to process)
queue depth → 0 (no new jobs because scheduler never dispatched them)

Both monitors show healthy. No scheduled tasks have run since the last deploy.

Queue worker died, scheduler still running:

schedule:run → runs ✓
ReportGeneration → dispatches jobs ✓
Horizon supervisor → dead ✗
queue depth → climbing ✗

The cron monitor shows green (scheduler ran). The queue is backing up because workers aren’t processing.

What full coverage looks like

Full coverage requires both layers:

Scheduler monitoring — hooks into Laravel’s native ScheduledTaskStarting, ScheduledTaskFinished, and ScheduledTaskFailed events. Records every task run with its exit code, duration, and output. Detects missed runs by comparing last-run timestamps against expected schedules.

Horizon monitoring — watches supervisor status directly from Redis, tracks queue depth per queue, and monitors job age. Alerts when a supervisor pauses or stops, before the queue backlog becomes a customer-visible problem.

These two layers cover the full stack: the scheduler that decides what to run, and the queue workers that actually process the jobs.

Implementation

Both are available in Crontinel. One install covers both:

composer require crontinel/laravel
php artisan crontinel:install

Add to your .env:

CRONTINEL_API_KEY=ct_your_key_here
CRONTINEL_ALERT_CHANNEL=slack
CRONTINEL_SLACK_WEBHOOK=https://hooks.slack.com/...

Scheduler events are captured automatically. Horizon monitoring runs on each polling cycle — no per-supervisor configuration needed.

The monitoring gap between “cron ran” and “jobs processed” is where silent failures live. Covering both layers closes it.

See also

blog
Why Generic Cron Monitors Miss Laravel Horizon Failures

Cronitor tells you a job ran. It can't tell you your Horizon supervisor is paused. Here's what's actually going on under the hood.

blog
Why Generic Uptime Monitors Miss Laravel Queue Failures

Your uptime monitor shows green while your queue depth climbs to 5,000 and customers wait. Here's why HTTP pings can't see Laravel queue failures, and what actually works.

vs
Crontinel vs Cronofy: Scheduling API vs Laravel Job Monitoring

Cronofy is a scheduling and calendar API. Crontinel monitors whether your scheduled jobs actually ran. Here's where they differ and when you need both.

vs
Crontinel vs LambdaTest: Testing Platform vs Laravel Job Monitoring

LambdaTest runs your test suite in the cloud. Crontinel monitors whether your production jobs are running. Here's the difference and when you need both.