How to Monitor Laravel Horizon Queue with Vigilmon
Laravel Horizon provides a beautiful dashboard for your Redis-powered queues. But when Horizon stops processing jobs, failed jobs pile up silently while your users experience delays. This guide shows how to monitor Laravel Horizon with Vigilmon.
Why Laravel Horizon Needs External Monitoring
Horizon can fail in ways invisible to traditional monitoring:
- Horizon process crash: The supervisor exits; no new jobs get processed
- Redis disconnection: Horizon loses its queue backend silently
- Memory limit exceeded: Horizon restarts but jobs are delayed during restart
- Deploy forgot to restart Horizon: New code deployed but old workers still running
None of these show up in HTTP access logs — your app returns 200 but background jobs are failing silently.
Creating a Health Check Route for Horizon
Add a Laravel route that exposes Horizon's status:
<?php
// routes/web.php or routes/api.php
use Illuminate\Support\Facades\Route;
use Laravel\Horizon\Contracts\MasterSupervisorRepository;
Route::get('/health/horizon', function () {
$supervisors = app(MasterSupervisorRepository::class)->all();
$isRunning = collect($supervisors)->contains(function ($supervisor) {
return $supervisor->status === 'running';
});
return response()->json([
'status' => $isRunning ? 'ok' : 'error',
'horizon' => $isRunning ? 'running' : 'stopped',
'supervisor_count' => count($supervisors),
'timestamp' => now()->toIso8601String()
], $isRunning ? 200 : 503);
})->middleware('throttle:60,1');
Or using the Horizon facade directly:
use Laravel\Horizon\Horizon;
Route::get('/health/horizon', function () {
if (Horizon::isRunning()) {
return response()->json([
'status' => 'ok',
'horizon' => 'running'
]);
}
return response()->json([
'status' => 'error',
'horizon' => 'not running'
], 503);
});
Extended Health Check Including Dependencies
Combine Horizon status with database and Redis checks:
Route::get('/health', function () {
$checks = [
'status' => 'ok',
'database' => 'ok',
'redis' => 'ok',
'horizon' => 'ok'
];
try {
DB::select('SELECT 1');
} catch (\Exception $e) {
$checks['database'] = 'error';
$checks['status'] = 'degraded';
}
try {
Redis::ping();
} catch (\Exception $e) {
$checks['redis'] = 'error';
$checks['status'] = 'degraded';
}
if (!\Laravel\Horizon\Horizon::isRunning()) {
$checks['horizon'] = 'stopped';
$checks['status'] = 'degraded';
}
$statusCode = $checks['status'] === 'ok' ? 200 : 503;
return response()->json($checks, $statusCode);
});
Setting Up Vigilmon Monitors
Monitor 1: Horizon Health Check
-
URL:
https://your-laravel-app.com/health/horizon - Method: GET
- Expected status: 200
-
Keyword check:
"horizon":"running" - Interval: 60 seconds
- Multi-region: Enabled
When Horizon stops, Vigilmon detects the 503 response and alerts immediately.
Monitor 2: Full Application Health
-
URL:
https://your-laravel-app.com/health - Method: GET
- Expected status: 200
-
Keyword check:
"status":"ok" - Interval: 60 seconds
Supervisor Configuration for Auto-restart
Configure Supervisor to automatically restart Horizon after crashes:
; /etc/supervisor/conf.d/horizon.conf
[program:horizon]
process_name=%(program_name)s
command=php /var/www/your-app/artisan horizon
autostart=true
autorestart=true
user=www-data
redirect_stderr=true
stdout_logfile=/var/log/horizon.log
stopwaitsecs=3600
Key settings:
-
autorestart=true: Supervisor restarts Horizon if it crashes -
stopwaitsecs=3600: Gives Horizon time to finish processing current jobs before stopping
Alert Configuration
Configure Vigilmon for Horizon downtime:
- Immediate: PagerDuty to on-call developer
- Slack: #queue-alerts channel
- Include in runbook: Check Supervisor status, restart command, Redis connectivity
Common Horizon Failure Scenarios
| Failure | Symptom | Vigilmon Detection |
|---|---|---|
| Horizon crash | 503 on /health/horizon | Immediate alert |
| Redis disconnection | 503 (Horizon stops) | Immediate alert |
| Memory limit | Brief restart gap | May catch brief downtime |
| Deploy without restart | Old workers running | 200 but jobs may fail |
Deployment Best Practices
Always restart Horizon after deployment:
# In your deploy script (Deployer, Envoyer, or custom)
php artisan horizon:terminate
# Supervisor auto-restarts it within seconds
Add to your GitHub Actions workflow:
- name: Restart Horizon
run: ssh deploy@your-server "cd /var/www/app && php artisan horizon:terminate"
Note: Use horizon:terminate not horizon:stop — terminate gracefully finishes current jobs before restarting.
Best Practices
- Expose a
/health/horizonendpoint that returns 503 when Horizon is stopped - Configure Supervisor with
autorestart=truefor automatic recovery - Always run
horizon:terminate(graceful) during deployments, nothorizon:stop - Monitor both the app health AND Horizon-specific health endpoints
- Include Redis health in your monitoring — Redis failure causes Horizon failure
Conclusion
Laravel Horizon is invisible when it fails — jobs pile up silently while your app appears healthy. Vigilmon catches Horizon downtime immediately with external HTTP monitoring and instant alerts.
Monitor your Laravel Horizon queue free at vigilmon.online
Top comments (0)