
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.
Horizon is a first-party Laravel package that provides a dashboard and supervisor for Redis queues. It shows you:
It's installed alongside your Laravel application and accessible at /horizon (with appropriate auth middleware in production).
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
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.
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));
});
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();
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.
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.
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.
Queue monitoring is one layer. You also need:
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.
Domain Monitor monitors your Laravel application's uptime alongside your Horizon health check. Add two monitors:
/health/horizon endpoint — catches queue processing failuresCreate 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.
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 moreYour 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 moreUptime 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 moreLooking to monitor your website and domains? Join our platform and start today.