Laravel Horizon Tuning: Supervisors &amp; Queue Metrics | Mohamed Said       [Skip to content](#main)  [ ![](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MH.png) Mohamed SaidLaravel Backend Engineer ](https://www.msaied.com/public) - [Home](https://www.msaied.com/public)
- [Projects](https://www.msaied.com/public/projects)
- [Articles](https://www.msaied.com/public/articles)
- [Certificates](https://www.msaied.com/public/certificates)
- [About](https://www.msaied.com/public#about)

           [  Contact](https://www.msaied.com/public#contact) Menu 

Menu
----

Close 

 - [HomeStart here](https://www.msaied.com/public)
- [ProjectsCase studies](https://www.msaied.com/public/projects)
- [ArticlesEngineering notes](https://www.msaied.com/public/articles)
- [CertificatesCredentials](https://www.msaied.com/public/certificates)
- [AboutHow I work](https://www.msaied.com/public#about)
- [ContactGet in touch](https://www.msaied.com/public#contact)

  [Start a conversation](https://www.msaied.com/public#contact) [WhatsApp](https://wa.me/201094619204) [Email](mailto:hello@msaied.com) 

 1. [Home](https://www.msaied.com/public)
2. /
3. [Articles](https://www.msaied.com/public/articles)
4. /
5. Laravel Horizon: Queue Metrics, Supervisor Tuning, and Reliable Job Throughput

 Laravel Horizon: Queue Metrics, Supervisor Tuning, and Reliable Job Throughput
===============================================================================

 Beyond the dashboard: how to tune Horizon supervisors, interpret queue metrics, handle job failures gracefully, and keep throughput predictable under real production load.

 ![](https://cdn.msaied.com/01M22N44A70A5MC2S599JP0MPH.webp) [Mohamed Said](https://www.msaied.com/public#person) Published 20 Jun 2026 · Updated 20 Jun 2026 · 3 min read

ShareCopy linkCopied

 ![Laravel Horizon: Queue Metrics, Supervisor Tuning, and Reliable Job Throughput](https://cdn.msaied.com/252/b2270936d9a6210705181ccc25587249.png) 

  On this page +1. [Laravel Horizon: Beyond the Pretty Dashboard](#laravel-horizon-beyond-the-pretty-dashboard)
2. [How Horizon's Supervisor Model Works](#how-horizons-supervisor-model-works)
3. [Separating Queues by Supervisor](#separating-queues-by-supervisor)
4. [Reading Horizon Metrics Correctly](#reading-horizon-metrics-correctly)
5. [Graceful Failure Handling](#graceful-failure-handling)
6. [Deployment Without Dropping Jobs](#deployment-without-dropping-jobs)
7. [Takeaways](#takeaways)

 Laravel Horizon: Beyond the Pretty Dashboard
--------------------------------------------

Most teams install Horizon, glance at the dashboard, and call it done. The real value is in understanding how Horizon's supervisor model maps to Redis queue semantics, how to read throughput metrics meaningfully, and how to prevent silent job loss under load.

### How Horizon's Supervisor Model Works

Horizon runs a master process that spawns one or more **supervisors**. Each supervisor manages a pool of worker processes for a specific queue (or set of queues). Workers are forked PHP processes — they boot the application once and then loop over jobs.

The key config lives in `config/horizon.php`:

```php
'environments' => [
    'production' => [
        'supervisor-default' => [
            'connection' => 'redis',
            'queue' => ['critical', 'default', 'low'],
            'balance' => 'auto',
            'autoScalingStrategy' => 'time',
            'minProcesses' => 2,
            'maxProcesses' => 20,
            'balanceCooldown' => 3,
            'tries' => 3,
            'timeout' => 90,
            'memory' => 256,
        ],
    ],
],

```

A few non-obvious details:

- **`balance => 'auto'`** uses the `autoScalingStrategy` to decide how to distribute workers. `time` scales based on wait time; `size` scales based on queue depth. For latency-sensitive queues, prefer `time`.
- **`balanceCooldown`** prevents thrashing. Three seconds is aggressive — consider 10–30 seconds for stable workloads.
- **`timeout`** must be shorter than your Redis `BLPOP` timeout and shorter than any upstream HTTP timeout in the job. A job that exceeds `timeout` is killed with `SIGKILL`, not gracefully.

### Separating Queues by Supervisor

Running `critical`, `default`, and `low` in a single supervisor means a burst on `low` can starve `critical`. Split them:

```php
'supervisor-critical' => [
    'queue' => ['critical'],
    'balance' => 'simple',
    'minProcesses' => 5,
    'maxProcesses' => 5, // fixed — always ready
    'tries' => 1,
    'timeout' => 30,
],
'supervisor-default' => [
    'queue' => ['default'],
    'balance' => 'auto',
    'autoScalingStrategy' => 'time',
    'minProcesses' => 2,
    'maxProcesses' => 15,
    'tries' => 3,
    'timeout' => 60,
],
'supervisor-low' => [
    'queue' => ['low'],
    'balance' => 'auto',
    'minProcesses' => 1,
    'maxProcesses' => 5,
    'tries' => 5,
    'timeout' => 120,
],

```

Fixed process counts on `critical` eliminate cold-start latency. Auto-scaling on `default` and `low` handles burst without wasting memory at idle.

### Reading Horizon Metrics Correctly

Horizon stores metrics in Redis under `horizon:` keys. The dashboard shows **throughput** (jobs/minute) and **runtime** (average execution time). Two traps:

1. **Throughput is a rolling average** — a spike followed by silence looks healthy. Export raw metrics to your APM (Datadog, New Relic) via the `Horizon::routeMailNotificationsTo` and snapshot approach, or query Redis directly.
2. **Runtime outliers are hidden** — the average masks P99 slowness. Instrument your jobs:

```php
public function handle(): void
{
    $start = hrtime(true);

    // ... job logic ...

    $ms = (hrtime(true) - $start) / 1e6;
    logger()->channel('metrics')->info('job.runtime', [
        'job' => static::class,
        'ms' => $ms,
    ]);
}

```

Ship these logs to a log aggregator and build P95/P99 dashboards there.

### Graceful Failure Handling

Horizon respects `$tries`, `$backoff`, and `$failOnTimeout`. Use them deliberately:

```php
class SendWebhookJob implements ShouldQueue
{
    public int $tries = 5;
    public bool $failOnTimeout = true;
    public int $timeout = 20;

    public function backoff(): array
    {
        return [10, 30, 60, 120, 300]; // exponential-ish
    }

    public function failed(Throwable $e): void
    {
        WebhookDelivery::markFailed($this->webhookId, $e->getMessage());
        // notify, alert, compensate
    }
}

```

Set `$failOnTimeout = true` so a hung job doesn't silently retry forever — it fails fast and triggers `failed()`.

### Deployment Without Dropping Jobs

Horizon workers are long-lived. On deploy:

```bash
php artisan horizon:terminate

```

This sends `SIGTERM` to the master, which propagates to workers. Workers finish their current job, then exit. Your process supervisor (Supervisor, systemd) restarts Horizon with the new code. Combine with a zero-downtime deploy tool (Envoyer, Deployer) to ensure the terminate fires after the new release is in place.

### Takeaways

- Split high-priority queues into dedicated supervisors with fixed process counts.
- Use `balance => 'auto'` with `autoScalingStrategy => 'time'` for latency-sensitive work.
- Set `failOnTimeout = true` and define `backoff()` arrays to avoid thundering-herd retries.
- Export raw job runtime to your APM — dashboard averages hide P99 pain.
- Always `horizon:terminate` on deploy; never `horizon:restart` alone in production.

- [laravel](https://www.msaied.com/public/articles?search=laravel)
- [horizon](https://www.msaied.com/public/articles?search=horizon)
- [queues](https://www.msaied.com/public/articles?search=queues)
- [redis](https://www.msaied.com/public/articles?search=redis)
- [performance](https://www.msaied.com/public/articles?search=performance)

 Frequently asked questions 
---------------------------

  What is the difference between `balance =&gt; 'auto'` and `balance =&gt; 'simple'` in Horizon?`simple` distributes workers evenly across queues regardless of load. `auto` dynamically reallocates workers based on either queue depth (`size` strategy) or wait time (`time` strategy), making it better for variable workloads.

   Why does my job get killed without calling the `failed()` method?If `timeout` is exceeded and `$failOnTimeout` is `false` (the default), Horizon kills the worker process with SIGKILL and does not invoke `failed()`. Set `public bool $failOnTimeout = true;` on your job class to ensure `failed()` is called and the job is marked as failed in the database.

   How do I prevent Horizon from losing jobs during a deployment?Run `php artisan horizon:terminate` as part of your deploy script after the new release is active. Horizon's master process signals workers to finish their current job and exit cleanly, then your process supervisor restarts Horizon with the updated codebase.

   ![Mohamed Said](https://cdn.msaied.com/01M22N44A70A5MC2S599JP0MPH.webp)About the author
----------------

[Mohamed Said](https://www.msaied.com/public#person)Senior Backend Engineer specializing in Laravel, scalable SaaS platforms, APIs, and cloud infrastructure. I build secure, high-performance web applications that help businesses grow.

[About](https://www.msaied.com/public#about) [GitHub ↗](https://github.com/EG-Mohamed) [LinkedIn ↗](https://www.linkedin.com/in/msaiedm/) [WhatsApp ↗](https://wa.me/201094619204) [Email Address ↗](mailto:hello@msaied.com) [My CV ↗](https://drive.google.com/file/u/0/d/1MF20IPRJyzfy32mhEutjL5EpSls0w2Q8/view)  

   [Previous articleMacros, Mixins, and Custom Collection Methods in Laravel](https://www.msaied.com/public/articles/macros-mixins-and-custom-collection-methods-in-laravel) [Next articleLivewire v3 Islands, Lazy Components, and Deferred Loading in Practice](https://www.msaied.com/public/articles/livewire-v3-islands-lazy-components-and-deferred-loading-in-practice)  

   On this page
-------------

1. [Laravel Horizon: Beyond the Pretty Dashboard](#laravel-horizon-beyond-the-pretty-dashboard)
2. [How Horizon's Supervisor Model Works](#how-horizons-supervisor-model-works)
3. [Separating Queues by Supervisor](#separating-queues-by-supervisor)
4. [Reading Horizon Metrics Correctly](#reading-horizon-metrics-correctly)
5. [Graceful Failure Handling](#graceful-failure-handling)
6. [Deployment Without Dropping Jobs](#deployment-without-dropping-jobs)
7. [Takeaways](#takeaways)

 ###  Have a technical challenge?

 Tell me what you’re building. I reply within two working days.

[Start a conversation](https://www.msaied.com/public#contact) 

   Related articles
-----------------

 [ ![](https://cdn.msaied.com/740/cce86edc21eddcbdd2f2454fadaf9c70.png)  · 3 min read### The Pipeline Pattern in Laravel: Custom Pipelines Beyond Middleware

5 Oct 2026 ](https://www.msaied.com/public/articles/the-pipeline-pattern-in-laravel-custom-pipelines-beyond-middleware-1) [ ![](https://cdn.msaied.com/739/2d6897fdcdcf090613f96f72a64b8a78.png)  · 4 min read### MySQL Full-Text Search in Laravel: Indexes, Relevance Scoring, and Boolean Mode

4 Oct 2026 ](https://www.msaied.com/public/articles/mysql-full-text-search-in-laravel-indexes-relevance-scoring-and-boolean-mode) [ ![](https://cdn.msaied.com/738/073696a3fefe18bec825beec5ac658f5.png)  · 4 min read### Laravel Queue Rate-Limited Middleware: Throttling Jobs Without Losing Work

4 Oct 2026 ](https://www.msaied.com/public/articles/laravel-queue-rate-limited-middleware-throttling-jobs-without-losing-work) 

  Have a technical challenge?
----------------------------

Tell me what you’re building. I reply within two working days.

 [Discuss your project ↗](https://www.msaied.com/public#contact) 

  © 2026 Mohamed Said · Built with Laravel, meant to last.Senior Backend Engineer specializing in Laravel, scalable SaaS platforms, APIs, and cloud infrastructure. I build secure, high-performance web applications that help businesses grow.

 - [Home](https://www.msaied.com/public)
- [Articles](https://www.msaied.com/public/articles)
- [Certificates](https://www.msaied.com/public/certificates)
- [GitHub](https://github.com/EG-Mohamed)
- [LinkedIn](https://www.linkedin.com/in/msaiedm/)
- [WhatsApp](https://wa.me/201094619204)
- [Email Address](mailto:hello@msaied.com)
- [My CV](https://drive.google.com/file/u/0/d/1MF20IPRJyzfy32mhEutjL5EpSls0w2Q8/view)
- [Sitemap](https://www.msaied.com/public/sitemap.xml)
