DEV Community

Vigilmon
Vigilmon

Posted on

How to Monitor Laravel Horizon Queue with Vigilmon

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');
Enter fullscreen mode Exit fullscreen mode

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);
});
Enter fullscreen mode Exit fullscreen mode

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);
});
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Add to your GitHub Actions workflow:

- name: Restart Horizon
  run: ssh deploy@your-server "cd /var/www/app && php artisan horizon:terminate"
Enter fullscreen mode Exit fullscreen mode

Note: Use horizon:terminate not horizon:stop — terminate gracefully finishes current jobs before restarting.

Best Practices

  1. Expose a /health/horizon endpoint that returns 503 when Horizon is stopped
  2. Configure Supervisor with autorestart=true for automatic recovery
  3. Always run horizon:terminate (graceful) during deployments, not horizon:stop
  4. Monitor both the app health AND Horizon-specific health endpoints
  5. 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)