Laravel Horizon Tuning: Supervisors &amp; Queue Metrics | Mohamed Said        [  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MH.png)   Mohamed Said Laravel Backend Engineer  ](https://msaied.com) [ Home ](https://msaied.com) [ Projects ](https://msaied.com/projects) [ Articles  ](https://msaied.com/articles) [ Certificates ](https://msaied.com/certificates) [ Contact ](https://msaied.com#contact-section) 

       [  ](https://github.com/EG-Mohamed)       

 [ Home ](https://msaied.com) [ Projects ](https://msaied.com/projects) [ Articles ](https://msaied.com/articles) [ Certificates ](https://msaied.com/certificates) [ Contact ](https://msaied.com#contact-section) 

  [ home ](https://msaied.com)    [ articles ](https://msaied.com/articles)    Laravel Horizon: Queue Metrics, Supervisor Tuning, and Reliable Job Throughput        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: Queue Metrics, Supervisor Tuning, and Reliable Job Throughput](https://cdn.msaied.com/252/b2270936d9a6210705181ccc25587249.png)

  #laravel   #horizon   #queues   #redis   #performance  

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

     20 Jun 2026      3 min read    ![Mohamed Said](https://cdn.msaied.com/01M22N44A70A5MC2S599JP0MPH.webp)  Mohamed Said  

       Table of contents

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

 Found this useful?

          [  ](https://twitter.com/intent/tweet?url=https%3A%2F%2Fmsaied.com%2Farticles%2Flaravel-horizon-queue-metrics-supervisor-tuning-and-reliable-job-throughput&text=Laravel+Horizon%3A+Queue+Metrics%2C+Supervisor+Tuning%2C+and+Reliable+Job+Throughput) [  ](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fmsaied.com%2Farticles%2Flaravel-horizon-queue-metrics-supervisor-tuning-and-reliable-job-throughput) 

 Frequently Asked Questions 
----------------------------

  3 questions  

     Q01  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. 

      Q02  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. 

      Q03  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. 

  Continue reading

 More Articles 
---------------

 [ View all    ](https://msaied.com/articles) 

 [ ![Practical RAG in Laravel: pgvector, Embeddings, and Retrieval-Augmented Generation](https://cdn.msaied.com/681/08058424f0e8433b83d9008c6b701cd8.png) laravel ai pgvector 

### Practical RAG in Laravel: pgvector, Embeddings, and Retrieval-Augmented Generation

Build a production-ready RAG pipeline in Laravel using pgvector, OpenAI embeddings, and a clean retrieval laye...

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

 19 Sep 2026     4 min read  

  Read    

 ](https://msaied.com/articles/practical-rag-in-laravel-pgvector-embeddings-and-retrieval-augmented-generation) [ ![Gate Responses, Policy Before-Hooks, and Ownership Guards in Laravel](https://cdn.msaied.com/680/65326929bb7b3e15cee4d9753000eddc.png) laravel authorization security 

### Gate Responses, Policy Before-Hooks, and Ownership Guards in Laravel

Beyond simple true/false gates: learn how to return rich Gate responses, intercept policies with before-hooks,...

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

 19 Sep 2026     3 min read  

  Read    

 ](https://msaied.com/articles/gate-responses-policy-before-hooks-and-ownership-guards-in-laravel) [ ![Fresh: A Laravel Package Skeleton with Testbench, CI, and Boost Integration](https://cdn.msaied.com/679/6e34855eba64d6cc443c5cb2e7d17555.png) Laravel Package Development Orchestra Testbench 

### Fresh: A Laravel Package Skeleton with Testbench, CI, and Boost Integration

Fresh is an opinionated Laravel package skeleton by Mazen Touati that ships with PHPUnit, Larastan, Rector, Pi...

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

 18 Sep 2026     3 min read  

  Read    

 ](https://msaied.com/articles/fresh-a-laravel-package-skeleton-with-testbench-ci-and-boost-integration) 

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

Explore

- [Home](https://msaied.com)
- [Projects](https://msaied.com/projects)
- [Articles](https://msaied.com/articles)
- [Certificates](https://msaied.com/certificates)
- [Contact](https://msaied.com#contact-section)

Connect

- [   hello@msaied.com ](mailto:hello@msaied.com)
- [   +20 109 461 9204 ](tel:+201094619204)

© 2026 Mohamed Said. All rights reserved.

 [  ](https://github.com/EG-Mohamed) [  ](https://www.linkedin.com/in/msaiedm/) [  ](https://wa.me/201094619204) [  ](mailto:hello@msaied.com) [  ](https://drive.google.com/file/u/0/d/1MF20IPRJyzfy32mhEutjL5EpSls0w2Q8/view)
