Laravel Horizon: Queue Tuning &amp; Graceful Scaling | 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 Queues in Production: Horizon Tuning, Metrics, and Graceful Scaling

 Laravel Queues in Production: Horizon Tuning, Metrics, and Graceful Scaling
============================================================================

 Go beyond basic queue setup. Learn how to tune Horizon supervisor processes, interpret queue metrics, handle backpressure, and scale workers gracefully without dropping jobs in production.

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

ShareCopy linkCopied

 ![Laravel Queues in Production: Horizon Tuning, Metrics, and Graceful Scaling](https://cdn.msaied.com/470/afd2bee56d15842d72c1c6d5c238d236.png) 

  On this page +1. [Laravel Horizon in Production: Beyond the Defaults](#laravel-horizon-in-production-beyond-the-defaults)
2. [Supervisor Configuration That Reflects Reality](#supervisor-configuration-that-reflects-reality)
3. [Reading Horizon Metrics Without Guessing](#reading-horizon-metrics-without-guessing)
4. [Handling Backpressure Without Losing Jobs](#handling-backpressure-without-losing-jobs)
5. [Graceful Shutdown: The Part Everyone Gets Wrong](#graceful-shutdown-the-part-everyone-gets-wrong)
6. [Takeaways](#takeaways)

 Laravel Horizon in Production: Beyond the Defaults
--------------------------------------------------

Most teams install Horizon, push the default config, and move on. That works until traffic spikes, jobs pile up, and the dashboard turns red. This article focuses on the levers that actually matter: supervisor balancing strategies, metric-driven scaling decisions, and graceful shutdown patterns that prevent job loss.

---

Supervisor Configuration That Reflects Reality
----------------------------------------------

Horizon's `config/horizon.php` ships with a single supervisor block. In production you almost always need multiple supervisors — one per logical job class or priority tier.

```php
'production' => [
    'supervisor-critical' => [
        'connection' => 'redis',
        'queue'      => ['critical'],
        'balance'    => 'auto',
        'minProcesses' => 2,
        'maxProcesses' => 10,
        'tries'      => 3,
        'timeout'    => 30,
        'nice'       => 0,
    ],
    'supervisor-default' => [
        'connection' => 'redis',
        'queue'      => ['default', 'notifications'],
        'balance'    => 'simple',
        'processes'  => 4,
        'tries'      => 5,
        'timeout'    => 90,
        'nice'       => 10, // lower OS priority than critical
    ],
    'supervisor-bulk' => [
        'connection' => 'redis',
        'queue'      => ['bulk-exports'],
        'balance'    => 'auto',
        'minProcesses' => 1,
        'maxProcesses' => 3,
        'balanceMaxShift'  => 1,
        'balanceCooldown'  => 5,
        'timeout'    => 600,
        'nice'       => 15,
    ],
],

```

**Key decisions here:**

- `balance => auto` lets Horizon shift processes between queues in the supervisor dynamically. Use it when queue depths fluctuate.
- `balanceMaxShift` limits how many processes can be added or removed per rebalance cycle — prevents thrashing.
- `nice` maps to the Unix process priority. Bulk jobs should yield CPU to critical ones.
- Separate `timeout` per supervisor. A 600-second timeout on the critical supervisor would mask hung jobs.

---

Reading Horizon Metrics Without Guessing
----------------------------------------

Horizon stores rolling throughput and runtime snapshots in Redis. You can query them programmatically:

```php
use Laravel\Horizon\Contracts\MetricsRepository;

$metrics = app(MetricsRepository::class);

$throughput = $metrics->throughputForQueue('default');  // jobs/min
$runtime    = $metrics->runtimeForQueue('default');     // avg ms

```

Pipe these into your observability stack (Datadog, Prometheus via a custom exporter, or even a simple scheduled command that writes to a log channel). Alert when throughput drops below your baseline or runtime climbs above your SLA threshold — not when the queue depth crosses an arbitrary number.

---

Handling Backpressure Without Losing Jobs
-----------------------------------------

When producers outpace consumers, the naive fix is "add more workers." The better fix is to model backpressure explicitly.

```php
// In a high-frequency dispatch path
use Illuminate\Support\Facades\Redis;

$depth = Redis::llen('queues:default');

if ($depth > 5000) {
    // Slow the producer, not just scale the consumer
    sleep(1); // or return a 429 to the upstream caller
}

Dispatch::dispatch(new ProcessWebhook($payload));

```

For HTTP-triggered jobs, returning a `202 Accepted` with a `Retry-After` header is more honest than silently queuing millions of jobs that will take hours to drain.

---

Graceful Shutdown: The Part Everyone Gets Wrong
-----------------------------------------------

Horizon sends `SIGTERM` to workers when you deploy. Workers finish their current job and exit — but only if your jobs respect the signal. Long-running jobs that ignore termination signals will be killed mid-execution.

```php
class ProcessLargeExport implements ShouldQueue
{
    public function handle(): void
    {
        foreach ($this->chunks() as $chunk) {
            if (app('queue.worker')->shouldQuit) {
                // Checkpoint state, re-dispatch remainder
                ProcessLargeExport::dispatch($this->remainingChunks());
                return;
            }

            $this->processChunk($chunk);
        }
    }
}

```

Set `HORIZON_TERMINATE_WAIT` (the `--stop-when-empty` equivalent) in your deploy script to give workers time to finish:

```bash
php artisan horizon:terminate
sleep 30  # match your longest expected job runtime
supervisorctl start horizon

```

---

Takeaways
---------

- Split supervisors by job priority and runtime profile, not just queue name.
- Use `balanceMaxShift` and `balanceCooldown` to prevent process thrashing under `auto` balance.
- Export Horizon metrics to your observability stack; alert on throughput and runtime, not raw depth.
- Model backpressure at the producer — slowing dispatch is safer than unbounded queue growth.
- Make long-running jobs checkpoint-aware so `SIGTERM` results in a safe re-dispatch, not data loss.

- [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)
- [performance](https://www.msaied.com/public/articles?search=performance)
- [production](https://www.msaied.com/public/articles?search=production)

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

  When should I use `balance =&gt; auto` vs `balance =&gt; simple` in Horizon?Use `auto` when a supervisor handles multiple queues with variable depth — Horizon will shift processes toward the busiest queue. Use `simple` for fixed-ratio allocation or single-queue supervisors where you want predictable process counts.

   How do I prevent jobs from being lost during a Horizon restart on deploy?Run `php artisan horizon:terminate` before starting the new release, and add a sleep in your deploy script equal to your longest expected job runtime. Ensure long-running jobs check `app('queue.worker')-&gt;shouldQuit` and checkpoint their state so they can safely re-dispatch remaining work.

   Is it safe to run multiple Horizon instances across servers?Yes. Each server runs its own Horizon process, and they coordinate through Redis. Ensure each server's `horizon.php` supervisor config reflects that server's role (e.g., bulk-export workers only on high-memory nodes) and that `APP\_NAME` is unique per server if you want separate dashboard entries.

   ![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 articleLaravel Horizon: Queue Metrics, Supervisor Tuning, and Graceful Scaling in Production](https://www.msaied.com/public/articles/laravel-horizon-queue-metrics-supervisor-tuning-and-graceful-scaling-in-production-1) [Next articleConcurrency in Laravel: Process Pools, Fibers, and the Concurrency Facade](https://www.msaied.com/public/articles/concurrency-in-laravel-process-pools-fibers-and-the-concurrency-facade)  

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

1. [Laravel Horizon in Production: Beyond the Defaults](#laravel-horizon-in-production-beyond-the-defaults)
2. [Supervisor Configuration That Reflects Reality](#supervisor-configuration-that-reflects-reality)
3. [Reading Horizon Metrics Without Guessing](#reading-horizon-metrics-without-guessing)
4. [Handling Backpressure Without Losing Jobs](#handling-backpressure-without-losing-jobs)
5. [Graceful Shutdown: The Part Everyone Gets Wrong](#graceful-shutdown-the-part-everyone-gets-wrong)
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/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)
