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/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  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) 

 [ ![Laravel AI SDK: Tool-Calling Agents and Conversation Persistence](https://cdn.msaied.com/260/8c84f424e42da01993c9ba4b8eb19655.png) laravel ai agents 

### Laravel AI SDK: Tool-Calling Agents and Conversation Persistence

Build reliable tool-calling AI agents in Laravel using the Prism package. Learn how to wire tools, persist con...

  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said 

 21 Jun 2026     3 min read  

  Read    

 ](https://msaied.com/articles/laravel-ai-sdk-tool-calling-agents-and-conversation-persistence) [ ![Laravel Livewire v3 Internals: Morph Markers, JS Hooks, and Alpine Integration](https://cdn.msaied.com/259/e8ce445f021c2b26ebe4dd5da50014f8.png) livewire laravel alpine 

### Laravel Livewire v3 Internals: Morph Markers, JS Hooks, and Alpine Integration

Go beyond the docs: understand how Livewire v3 diffs the DOM with morph markers, intercept the lifecycle with...

  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said 

 21 Jun 2026     3 min read  

  Read    

 ](https://msaied.com/articles/laravel-livewire-v3-internals-morph-markers-js-hooks-and-alpine-integration) [ ![Laravel Package Development: Service Providers, Auto-Discovery, and Config Merging](https://cdn.msaied.com/258/673a80fa8e42ae375a4bba21bdcd92ea.png) laravel packages service-providers 

### Laravel Package Development: Service Providers, Auto-Discovery, and Config Merging

Build a production-ready Laravel package from scratch — covering service provider design, auto-discovery via c...

  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said 

 21 Jun 2026     3 min read  

  Read    

 ](https://msaied.com/articles/laravel-package-development-service-providers-auto-discovery-and-config-merging-1) 

   [  ![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)
