Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Horizon Laravel Package

laravel/horizon

Laravel Horizon provides a beautiful dashboard and code-driven configuration for Redis queues, letting you monitor job throughput, runtime, and failures while keeping worker settings in source control.

View on GitHub
Deep Wiki
Context7

Getting Started

Horizon is a Redis-only queue dashboard and worker supervisor. If your queues run on database, sqs, or beanstalkd, Horizon will not help you — switch the connection to Redis first.

1. Install and publish

composer require laravel/horizon
php artisan horizon:install

This publishes config/horizon.php, the Horizon service provider, and the dashboard assets. Commit config/horizon.php to source control — it is the whole point of the package.

2. Point your queue at Redis

// config/queue.php
'default' => env('QUEUE_CONNECTION', 'redis'),

'redis' => [
    'driver' => 'redis',
    'connection' => 'default',
    'queue' => env('REDIS_QUEUE', 'default'),
    'retry_after' => 90,
    'block_for' => null,
],

Ensure REDIS_CLIENT is set (phpredis or predis) and that ext-redis is installed where relevant — recent versions fail fast at boot when the extension is missing.

3. Define a supervisor in config/horizon.php

'environments' => [
    'production' => [
        'supervisor-1' => [
            'connection' => 'redis',
            'queue' => ['default', 'emails'],
            'balance' => 'auto',
            'maxProcesses' => 10,
            'minProcesses' => 1,
            'tries' => 3,
            'timeout' => 60,
        ],
    ],
    'local' => [
        'supervisor-1' => [
            'connection' => 'redis',
            'queue' => ['default'],
            'balance' => 'simple',
            'processes' => 3,
        ],
    ],
],

4. Run it

php artisan horizon

Visit /horizon. The first thing you should look at is the Metrics tab — job throughput, wait time, and runtime per queue. That is the fastest signal that your supervisor sizing is wrong.

First use case: a single default queue with balance => 'auto', one supervisor, and a dashboard bookmark. Everything else (tags, silencing, notifications, trimming) is refinement layered on top.

Implementation Patterns

Supervisor per workload, not per queue. The idiomatic setup is one supervisor per job shape, not one per queue name. Long-running imports get their own supervisor with a low maxProcesses and long timeout; fast interactive jobs get a high-concurrency supervisor. Queues can appear in multiple supervisors only if you understand that two supervisors polling the same queue will double-process it.

balance => 'auto' with minProcesses/maxProcesses. This is the pattern worth learning. Horizon scales each queue within a supervisor based on backlog, then reduces to minProcesses when idle. Pair it with balanceMaxShift and balanceCooldown to prevent thrashing:

'balance' => 'auto',
'autoScalingStrategy' => 'time', // or 'size'
'balanceMaxShift' => 1,
'balanceCooldown' => 3,

Separate the dispatcher and the worker. In production, php artisan horizon runs under Supervisor/systemd as your worker fleet; your web process never runs queue:work. Horizon replaces queue:work entirely — do not run both against the same queues.

Deploy flow. On each release:

php artisan horizon:terminate   # graceful shutdown, supervisor restarts

horizon:terminate is the deploy hook. It signals the master process to finish in-flight jobs and exit; the process manager restarts it with the new code. Do not kill -9 and do not rely on queue:restart — Horizon has its own master loop.

Job tags for searchability. Tags drive the dashboard's job search. Without them, tracing an order through the queue is painful:

class ProcessOrder implements ShouldQueue
{
    public function tags(): array
    {
        return ['order:'.$this->order->id, 'customer:'.$this->order->customer_id];
    }
}

Tags are indexed in Redis under horizon:job:<id> and the tag index. Keep them low-cardinality and stable — random UUIDs per job will bloat the index without helping search.

Silencing noisy jobs. High-volume jobs drown the dashboard. config/horizon.php accepts:

'silenced' => [
    App\Jobs\PingHeartbeat::class,
],

Silenced jobs still run and still appear in metrics aggregates, but are excluded from the jobs/retries lists.

Failure notifications. Wire Horizon::routeMailNotificationsTo() / routeSlackNotificationsTo() in a service provider, and set per-waiter notify thresholds. This is the difference between "we noticed at 3am" and "the customer noticed".

Waits and SLAs. Define wait thresholds for alerting on backlog:

'waits' => [
    'redis:default' => 60,
    'redis:emails' => 300,
],

Metrics trimming. By default metrics are retained for 24 hours (metrics.trim_snapshots). If your Redis instance is memory-constrained, shorten the window and set trim_recent_jobs, trim_failed, trim_recent_failed, trim_monitored_jobs aggressively. horizon:clear-metrics exists for manual purges — note that older versions had bugs clearing with PhpRedis key prefixes; keep the package current.

Local development. Horizon works locally but is usually overkill. Use horizon:listen (available in recent releases) for a lightweight listener with file watching, or just run queue:work locally. The localStorage key for dashboard preferences is namespaced to avoid collisions when multiple apps run on localhost ports.

Monitoring endpoint integration. Horizon::auth() is where you gate the dashboard in non-local environments:

// app/Providers/HorizonServiceProvider.php
protected function gate(): void
{
    Gate::define('viewHorizon', fn ($user) => in_array($user->email, [
        '[email protected]',
    ]));
}

For uptime checks, hit /horizon/api/stats behind the same gate, or use the horizon:status/horizon:supervisor-status commands in your health probe.

Gotchas and Tips

Horizon is Redis-only, full stop. There is no SQS or database support and no plugin path. If you need mixed connections, run Horizon for the Redis workload and queue:work for the rest — but understand your dashboards are now split.

Do not run queue:work alongside Horizon on the same queue. Both consume from the same Redis list and will steal each other's jobs, breaking your supervisor's process accounting and Horion's metrics.

retry_after must exceed timeout. This is the single most common production incident. If retry_after (in config/queue.php) is less than a job's timeout (in config/horizon.php), the job is retried while still running, and you get duplicate execution. Rule of thumb: retry_after = timeout + 10.

horizon:terminate vs horizon:pause. Terminate = graceful restart for deploys. Pause = stop accepting new jobs without stopping workers. horizon:pause-supervisor exists for a single supervisor. Don't confuse them on a deploy script.

horizon:publish is deprecated. Assets ship with the package via a service provider and Vite-built manifests. Running the old publish command may leave stale assets; if the dashboard looks unstyled after an upgrade, clear your view cache and check vendor/laravel/horizon/public rather than re-publishing.

The redis connection must be a real standalone/primary, not a read replica. Horizon writes its master and worker heartbeats into Redis. Cluster setups are supported as of recent 5.4x releases, but prefix handling (REDIS_PREFIX) historically broke metric scanning and horizon:clear-metrics; keep an eye on prefix behavior when upgrading.

Metrics are approximate and sampled. Horizon writes snapshots at intervals (metrics.trim_snapshots window, snapshot job). Spiky dashboards after a code deploy are usually just a gap between snapshots, not a real failure. Do not tune supervisors based on a single 5-minute window.

maxProcesses exceeds CPU and you'll see it. auto balancing will happily spawn maxProcesses workers; if that's 200 on a 4-core box, every job gets slower and retry_after breaches become self-fulfilling. Size to cores × ~1.5 for IO-bound jobs, less for CPU-bound.

PHP CLI memory limit is a Horizon concern, not just a job concern. Each supervisor holds the framework in memory. A 128M CLI limit with a fat app will OOM before your job does. Check php -i | grep memory_limit on the worker host.

horizon:listen had a runaway memory issue fixed in 5.45.1 — if you rely on it locally, pin to a version after that fix.

Job code is not reloaded on file change in production. Deploy → horizon:terminate → supervisor restarts → new code. Forgetting the terminate step is why "I deployed the fix and it's still failing" happens; the old supervisor is still processing with the old opcache.

Failed jobs live in failed_jobs (or horizon_failed_jobs if you customized the table) and are visible in the dashboard, but retrying from the UI dispatches a fresh job — it does not replay the original payload state. Mutating payloads between failure and retry will surprise you.

Rate limiting across supervisors. RateLimited middleware counters are per-queue-connection, not per-supervisor. Multiple supervisors on the same queue share the limit, which is usually what you want but is not obvious.

Extension points. Horizon::auth() for dashboard gating; Horizon::use() to swap the master supervisor; the HorizonRestarting/MasterSupervisorLooped events for custom telemetry; and config/horizon.php's middleware key to inject job middleware globally. If you need custom dashboard tabs, you're in "fork it" territory — the dashboard is a Vue SPA compiled into the package, not a Blade-in-package surface.

CSP nonce. Newer releases expose a CSP nonce option for the dashboard's inline styles/scripts. If you enforce a strict Content-Security-Policy, the dashboard will render blank without it — configure the nonce rather than weakening the header.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
robertmarney/lara-hierarchial-collections
simonschaufi/rule-doc-generator
emissaoja/emissaoja
blackbricksoftware/gravityforms-civicrm-form-processor
al-rahabi/fancy-crud
jinas/dhivatar-component
mltstephane/laravel-quality
bernskiold/laravel-talentlms
bernskioldmedia/laravel-form-architect
youbar/easy-crud
c-point-labs/laravel-hostyourai
dbravoan/dbcms-admin-theme-dbcms
dbravoan/dbcms-theme-db-cyber
dbravoan/dbcms-blocks-basic
dbravoan/dbcms-module-content-audit
dbravoan/dbcms-module-ai-assistant
paygate-to/paygate-php
padosoft/askmydocs-connector-base
defstudio/supercharged-carbon
bernskioldmedia/laravel-matomo