Laravel Horizon dashboard showing queue throughput, failed jobs and worker status in a dark terminal-style interface
# developer tools# website monitoring

How to Monitor Laravel Horizon Queues and Failed Jobs

Background queues are easy to forget about. Your web application is up, users can log in, pages load — and meanwhile your queue workers have been failing silently for hours. Emails aren't being sent. Reports aren't generating. Scheduled jobs are piling up in the failed jobs table.

Laravel Horizon gives you visibility into your Redis-backed queues. Combined with proper uptime monitoring, you can catch queue problems before they turn into user-facing incidents.

What Laravel Horizon Does

Horizon is a first-party Laravel package that provides a dashboard and supervisor for Redis queues. It shows you:

  • Queue throughput and wait times
  • Active, pending, and completed jobs
  • Failed jobs with stack traces
  • Worker status and memory usage

It's installed alongside your Laravel application and accessible at /horizon (with appropriate auth middleware in production).

Setting Up Horizon

Install via Composer:

composer require laravel/horizon
php artisan horizon:install
php artisan migrate

Configure queues in config/horizon.php:

'environments' => [
    'production' => [
        'supervisor-1' => [
            'maxProcesses' => 10,
            'balanceMaxShift' => 1,
            'balanceCooldown' => 3,
        ],
    ],
],

Start Horizon:

php artisan horizon

In production, run Horizon as a supervised process with Supervisor or similar:

[program:horizon]
process_name=%(program_name)s
command=php /var/www/artisan horizon
autostart=true
autorestart=true
user=www-data
redirect_stderr=true
stdout_logfile=/var/log/horizon.log
stopwaitsecs=3600

Monitoring Failed Jobs

Failed jobs are the primary signal that something has gone wrong in your queue processing. Horizon's dashboard shows them, but you need a way to be alerted rather than having to check manually.

Email Alerts on Failure

Laravel's queue system fires events when jobs fail. Hook into the Queue::failing event:

// In AppServiceProvider::boot()
use Illuminate\Support\Facades\Queue;
use Illuminate\Queue\Events\JobFailed;

Queue::failing(function (JobFailed $event) {
    // Send alert email, Slack notification, etc.
    Log::error('Queue job failed', [
        'job' => $event->job->resolveName(),
        'exception' => $event->exception->getMessage(),
        'queue' => $event->job->getQueue(),
    ]);

    // Or notify your team:
    // Notification::route('mail', '[email protected]')
    //     ->notify(new JobFailedNotification($event));
});

Monitoring Failed Job Count

A single failed job might be a transient error. A growing failed jobs table is a symptom of a systemic problem. Track the count:

// Artisan command to check failed job counts
use Illuminate\Support\Facades\DB;

$failedCount = DB::table('failed_jobs')
    ->where('failed_at', '>=', now()->subHour())
    ->count();

if ($failedCount > 10) {
    // Alert — too many failures in the last hour
}

Schedule this as a regular health check:

// In Console/Kernel.php
$schedule->command('queue:failed-check')->everyFiveMinutes();

Horizon Health Check Endpoint

The most important monitoring addition is a health check endpoint that confirms Horizon is running:

// In routes/web.php or routes/api.php
Route::get('/health/horizon', function () {
    $status = \Laravel\Horizon\Contracts\MasterSupervisorRepository::class;
    $masters = app($status)->all();

    if (empty($masters)) {
        return response()->json([
            'status' => 'error',
            'message' => 'Horizon is not running'
        ], 503);
    }

    $failedLastHour = \DB::table('failed_jobs')
        ->where('failed_at', '>=', now()->subHour())
        ->count();

    return response()->json([
        'status' => 'ok',
        'horizon' => 'running',
        'failed_last_hour' => $failedLastHour,
    ]);
})->middleware('auth.basic.once');

Point an uptime monitor at /health/horizon to verify Horizon is actively running, not just that your web server is up.

Cron Job Monitoring

Laravel's task scheduler (managed via php artisan schedule:run triggered by a cron) can fail silently. The cron job stops, scheduled tasks don't run, but your web application continues working — and you might not notice for days.

Use heartbeat-style monitoring for your scheduler. Add a ping to a monitoring service at the end of critical scheduled jobs:

$schedule->command('reports:generate')
    ->daily()
    ->thenPing('https://your-heartbeat-url');

See how to monitor cron jobs for the full approach.

Queue Worker Memory Leaks

PHP isn't designed for long-running processes by default. Queue workers can accumulate memory over time, eventually hitting their memory limit and stopping. Horizon handles this by restarting workers when they exceed the configured memory limit:

// config/horizon.php
'memory' => 128, // MB — restart workers that exceed this

Monitor for workers that are restarting frequently — repeated restarts indicate a memory leak in a job class that needs investigation.

Connecting to the Wider Monitoring Cluster

Queue monitoring is one layer. You also need:

  • Uptime monitoring for your application HTTP layer
  • Application-level health checks that test the database and cache
  • Alerts when the queue grows excessively (jobs piling up means workers aren't keeping up)

If you're deploying Laravel on Railway or Render, those platforms add their own monitoring layer — but they don't monitor Horizon specifically, so the health check endpoint approach is still required.

Uptime Monitoring for Laravel Applications

Domain Monitor monitors your Laravel application's uptime alongside your Horizon health check. Add two monitors:

  1. Your main application URL — catches web server and application failures
  2. Your /health/horizon endpoint — catches queue processing failures

Create a free account and set both up. You'll get immediate alerts if either the web layer or the queue layer goes down — covering the two most common failure modes for Laravel applications.

For broader Laravel monitoring guidance, see how to monitor Laravel applications and uptime monitoring best practices. If you're running queue workers across multiple frameworks — or want to see how the Horizon approach compares to BullMQ and Celery — see how to monitor queue workers on Laravel, Node.js, and Python apps.

Also in This Series

More posts

Why Your Status Page Matters During an Outage

When your site goes down, your status page becomes the most important page you have. Here's why it matters, what happens when you don't have one, and what a good status page does during a real outage.

Read more
Why Your Domain Points to the Wrong Server

Your domain is resolving, but pointing to the wrong server — showing old content, a previous host's page, or someone else's site entirely. Here's what causes this and how to diagnose it.

Read more
Why Website Monitoring Misses Downtime Sometimes

Uptime monitoring isn't foolproof. Single-location monitors, wrong health check endpoints, long check intervals, and false positives can all cause real downtime to go undetected. Here's what to watch out for.

Read more

Subscribe to our PRO plan.

Looking to monitor your website and domains? Join our platform and start today.