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

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

Menu
----

Close 

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

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

 1. [Home](https://www.msaied.com)
2. /
3. [Articles](https://www.msaied.com/articles)
4. /
5. Laravel Horizon Deep Dive: Queue Metrics, Supervisor Tuning, and Graceful Scaling

 Laravel Horizon Deep Dive: Queue Metrics, Supervisor Tuning, and Graceful Scaling
==================================================================================

 Beyond the dashboard: learn how to tune Horizon supervisors for burst traffic, expose queue metrics to your monitoring stack, and handle graceful worker termination without dropping jobs.

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

ShareCopy linkCopied

 ![Laravel Horizon Deep Dive: Queue Metrics, Supervisor Tuning, and Graceful Scaling](https://cdn.msaied.com/798/6a3e8f3b911d933877fa7335fb546431.png) 

  On this page +1. [Why the Default Horizon Config Leaves Performance on the Table](#why-the-default-horizon-config-leaves-performance-on-the-table)
2. [Supervisor Topology: One Size Does Not Fit All](#supervisor-topology-one-size-does-not-fit-all)
3. [The nice Option for Background Work](#the-codenicecode-option-for-background-work)
4. [Exporting Horizon Metrics Without a Paid APM](#exporting-horizon-metrics-without-a-paid-apm)
5. [Graceful Termination: Not Losing Jobs During Deploys](#graceful-termination-not-losing-jobs-during-deploys)
6. [Takeaways](#takeaways)

 Why the Default Horizon Config Leaves Performance on the Table
--------------------------------------------------------------

Most teams ship Horizon with the generated `config/horizon.php`, set `balance` to `auto`, and call it done. That works fine at low volume. Under burst traffic — flash sales, scheduled report generation, webhook floods — the defaults cause queue depth to spike while workers sit idle on quiet queues. This article focuses on three levers that actually matter: supervisor topology, balance strategy tuning, and exporting metrics to an external monitoring stack.

---

Supervisor Topology: One Size Does Not Fit All
----------------------------------------------

Horizon's `supervisors` key maps to separate Redis consumer groups. Treat each supervisor as a dedicated pool for a traffic class.

```php
// config/horizon.php
'environments' => [
    'production' => [
        'supervisor-critical' => [
            'connection' => 'redis',
            'queue'      => ['critical', 'default'],
            'balance'    => 'simple',
            'processes'  => 10,
            'tries'      => 3,
            'timeout'    => 60,
        ],
        'supervisor-bulk' => [
            'connection' => 'redis',
            'queue'      => ['bulk', 'reports'],
            'balance'    => 'auto',
            'minProcesses' => 2,
            'maxProcesses' => 20,
            'balanceCooldown' => 3,
            'balanceMaxShift' => 5,
            'tries'      => 1,
            'timeout'    => 300,
        ],
    ],
],

```

**Key decisions here:**

- `critical` uses `simple` balance with a fixed process count — predictable latency, no scale-down lag.
- `bulk` uses `auto` with `balanceCooldown` of 3 seconds and `balanceMaxShift` of 5 — Horizon can add at most 5 workers per rebalance cycle, preventing thundering-herd process spawning.
- Long-running report jobs get their own supervisor with a 300-second timeout, isolated from the 60-second critical pool.

### The `nice` Option for Background Work

For truly low-priority work, set `'nice' => 10` on a supervisor. The OS scheduler deprioritises those workers, leaving CPU headroom for web processes during peak load.

---

Exporting Horizon Metrics Without a Paid APM
--------------------------------------------

Horizon stores throughput and runtime snapshots in Redis. You can read them directly and push to Prometheus via a scheduled command.

```php
// app/Console/Commands/ExportHorizonMetrics.php
use Laravel\Horizon\Contracts\MetricsRepository;
use Illuminate\Console\Command;

class ExportHorizonMetrics extends Command
{
    protected $signature = 'horizon:export-metrics';

    public function handle(MetricsRepository $metrics): void
    {
        $queues = ['critical', 'default', 'bulk', 'reports'];

        foreach ($queues as $queue) {
            $throughput = $metrics->throughputForQueue($queue);
            $runtime    = $metrics->runtimeForQueue($queue);

            // Push to your Prometheus pushgateway or StatsD
            app('statsd')->gauge("horizon.throughput.{$queue}", $throughput);
            app('statsd')->gauge("horizon.runtime.{$queue}", $runtime);
        }
    }
}

```

Schedule this every minute in `routes/console.php`:

```php
Schedule::command('horizon:export-metrics')->everyMinute()->withoutOverlapping();

```

This gives you queue throughput and average job runtime as time-series data — enough to build a Grafana dashboard that alerts before queue depth becomes a user-visible problem.

---

Graceful Termination: Not Losing Jobs During Deploys
----------------------------------------------------

Horizon sends `SIGTERM` to workers on `horizon:terminate`. By default, a worker finishes its current job then exits. The risk is a deploy that kills the master process before workers drain.

In your deploy script, always:

```bash
php artisan horizon:terminate
sleep 5  # give workers time to finish current jobs
supervisorctl restart horizon

```

For jobs that must not be interrupted, implement `ShouldBeUnique` or check `app()->isDownForMaintenance()` inside the job's `handle()` to re-queue cleanly:

```php
public function handle(): void
{
    if (app()->isDownForMaintenance()) {
        $this->release(30); // re-queue, try again in 30 s
        return;
    }
    // ... actual work
}

```

---

Takeaways
---------

- **Separate supervisors by traffic class** — don't mix fast, low-latency jobs with slow bulk work in one pool.
- **Use `balanceCooldown` and `balanceMaxShift`** to prevent Horizon from spawning dozens of workers in a single rebalance cycle.
- **`MetricsRepository` is public API** — read it in a scheduled command and push to your existing monitoring stack instead of relying solely on the Horizon UI.
- **Graceful termination requires a deliberate deploy sequence** — `horizon:terminate` + sleep + restart, not a hard process kill.
- **`nice` is a free win** for background supervisors that compete with web workers for CPU.

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

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

  What is the difference between `simple`, `auto`, and `false` balance strategies in Horizon?`simple` splits processes evenly across queues in the supervisor. `auto` dynamically reallocates processes based on queue depth, respecting `minProcesses`, `maxProcesses`, `balanceCooldown`, and `balanceMaxShift`. `false` disables rebalancing entirely, keeping a fixed process count per queue as defined in the `queue` array order.

   Can I run multiple Horizon instances pointing at the same Redis for different environments?Yes, but you must set a unique `prefix` in `config/horizon.php` for each instance (e.g., `staging-horizon:` vs `production-horizon:`). Without distinct prefixes, supervisors from different environments will conflict and overwrite each other's state in Redis.

   How do I prevent a long-running job from blocking a worker past its timeout?Set `timeout` on the supervisor to slightly less than the job's expected maximum runtime, and ensure the job itself respects PHP's `set\_time\_limit()`. Horizon sends SIGTERM at the timeout threshold; if the job catches it and re-queues, you avoid silent failures. For truly unbounded work, break the job into smaller chained jobs instead.

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

[Mohamed Said](https://www.msaied.com#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#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 articleEloquent Custom Casts: Encrypting, Compressing, and Composing Value Objects at the Model Layer](https://www.msaied.com/articles/eloquent-custom-casts-encrypting-compressing-and-composing-value-objects-at-the-model-layer)  

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

1. [Why the Default Horizon Config Leaves Performance on the Table](#why-the-default-horizon-config-leaves-performance-on-the-table)
2. [Supervisor Topology: One Size Does Not Fit All](#supervisor-topology-one-size-does-not-fit-all)
3. [The nice Option for Background Work](#the-codenicecode-option-for-background-work)
4. [Exporting Horizon Metrics Without a Paid APM](#exporting-horizon-metrics-without-a-paid-apm)
5. [Graceful Termination: Not Losing Jobs During Deploys](#graceful-termination-not-losing-jobs-during-deploys)
6. [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#contact) 

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

 [ ![](https://cdn.msaied.com/796/13df3306c87eeeccf69b380e7a07d8da.png)  · 3 min read### Eloquent Custom Casts: Encrypting, Compressing, and Composing Value Objects at the Model Layer

19 Oct 2026 ](https://www.msaied.com/articles/eloquent-custom-casts-encrypting-compressing-and-composing-value-objects-at-the-model-layer) [ ![](https://cdn.msaied.com/797/2903a8f469c2bc085780ed09b63d2dd9.png) Laravel · 4 min read### Run Large PlanetScale Queries in Laravel With DB::olap()

19 Oct 2026 ](https://www.msaied.com/articles/run-large-planetscale-queries-in-laravel-with-dbolap) [ ![](https://cdn.msaied.com/795/9d9dd20c259ecc6356a91021a872695c.png)  · 3 min read### Eloquent Query Optimization: Defeating N+1 Problems at the Source

18 Oct 2026 ](https://www.msaied.com/articles/eloquent-query-optimization-defeating-n1-problems-at-the-source) 

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

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

 [Discuss your project ↗](https://www.msaied.com#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)
- [Articles](https://www.msaied.com/articles)
- [Certificates](https://www.msaied.com/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/sitemap.xml)
