Laravel Horizon: Queue Tuning &amp; Job Reliability | 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 Deep Dive: Queue Tuning, Supervisor Strategies, and Job Reliability        On this page       1. [  Why the Default Horizon Config Will Bite You in Production ](#why-the-default-horizon-config-will-bite-you-in-production)
2. [  Supervisor Strategies: Separate Concerns by Queue ](#supervisor-strategies-separate-concerns-by-queue)
3. [  Long-Running Jobs: Timeout, Memory, and the Daemon Loop ](#long-running-jobs-timeout-memory-and-the-daemon-loop)
4. [  Memory Leak Mitigation ](#memory-leak-mitigation)
5. [  Instrumenting Queues: Events and Metrics ](#instrumenting-queues-events-and-metrics)
6. [  Takeaways ](#takeaways)

  ![Laravel Horizon Deep Dive: Queue Tuning, Supervisor Strategies, and Job Reliability](https://cdn.msaied.com/186/198e117fc9baa8699ff73709f64a3c79.png)

  #laravel   #horizon   #queues   #performance  

 Laravel Horizon Deep Dive: Queue Tuning, Supervisor Strategies, and Job Reliability 
=====================================================================================

     15 Jun 2026      3 min read    ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said  

       Table of contents

1. [  01   Why the Default Horizon Config Will Bite You in Production  ](#why-the-default-horizon-config-will-bite-you-in-production)
2. [  02   Supervisor Strategies: Separate Concerns by Queue  ](#supervisor-strategies-separate-concerns-by-queue)
3. [  03   Long-Running Jobs: Timeout, Memory, and the Daemon Loop  ](#long-running-jobs-timeout-memory-and-the-daemon-loop)
4. [  04   Memory Leak Mitigation  ](#memory-leak-mitigation)
5. [  05   Instrumenting Queues: Events and Metrics  ](#instrumenting-queues-events-and-metrics)
6. [  06   Takeaways  ](#takeaways)

 Why the Default Horizon Config Will Bite You in Production
----------------------------------------------------------

The `horizon.php` stub ships with a single `default` supervisor, `auto` balance mode, and `maxProcesses: 1`. That is fine for a demo. In production it means one slow job can stall your entire queue, memory leaks accumulate silently, and you have no visibility into which queue is the bottleneck.

This article walks through the decisions that actually matter.

---

Supervisor Strategies: Separate Concerns by Queue
-------------------------------------------------

Horizon's supervisor config is the most important lever you have. Group queues by their latency contract, not by convenience.

```php
// config/horizon.php
'environments' => [
    'production' => [
        'supervisor-critical' => [
            'connection' => 'redis',
            'queue'      => ['critical'],
            'balance'    => 'simple',
            'processes'  => 10,
            'tries'      => 3,
            'timeout'    => 30,
            'memory'     => 256,
        ],
        'supervisor-default' => [
            'connection' => 'redis',
            'queue'      => ['default', 'notifications'],
            'balance'    => 'auto',
            'minProcesses' => 2,
            'maxProcesses' => 20,
            'balanceMaxShift'    => 5,
            'balanceCooldown'    => 3,
            'tries'      => 5,
            'timeout'    => 90,
            'memory'     => 256,
        ],
        'supervisor-heavy' => [
            'connection' => 'redis',
            'queue'      => ['exports', 'reports'],
            'balance'    => 'simple',
            'processes'  => 3,
            'tries'      => 2,
            'timeout'    => 600,
            'memory'     => 512,
        ],
    ],
],

```

**`simple` vs `auto` balance:** `simple` distributes processes evenly across queues in the list. `auto` watches queue depth and shifts workers toward the busiest queue. Use `auto` when queue depths are unpredictable; use `simple` when you need deterministic allocation.

`balanceMaxShift` and `balanceCooldown` prevent thrashing — Horizon will move at most 5 workers per 3-second cycle.

---

Long-Running Jobs: Timeout, Memory, and the Daemon Loop
-------------------------------------------------------

The `timeout` value in Horizon config maps directly to the `--timeout` flag passed to `queue:work`. When a job exceeds this, the worker process is killed via SIGKILL — not a graceful exception. Your job must be idempotent.

```php
class GenerateExportJob implements ShouldQueue
{
    public int $timeout = 540;   // job-level override
    public int $tries   = 2;
    public bool $failOnTimeout = true;

    public function handle(): void
    {
        // Checkpoint progress so a retry can resume
        Cache::put("export:{$this->exportId}:cursor", $this->cursor);

        // ... heavy work ...
    }

    public function retryUntil(): \DateTime
    {
        return now()->addHours(2);
    }
}

```

Set `$failOnTimeout = true` (Laravel 10+) so the job is marked failed rather than silently retried forever.

---

Memory Leak Mitigation
----------------------

Horizon workers are long-lived PHP processes. Every static cache, event listener registered inside a job, or Eloquent model that holds a reference will accumulate.

```php
// In a job that processes many models
public function handle(): void
{
    User::query()
        ->lazyById(200)
        ->each(function (User $user) {
            $this->process($user);
            // Detach the model from the identity map
        });

    // Force GC after heavy work
    gc_collect_cycles();
}

```

Set `memory` in the supervisor config to a value your server can sustain multiplied by `maxProcesses`. Horizon will restart a worker that exceeds its memory limit after the current job finishes.

---

Instrumenting Queues: Events and Metrics
----------------------------------------

Horizon emits first-class events. Hook into them for custom alerting without polling the dashboard.

```php
// app/Providers/AppServiceProvider.php
use Laravel\Horizon\Events\JobFailed;
use Laravel\Horizon\Events\LongWaitDetected;

public function boot(): void
{
    Event::listen(LongWaitDetected::class, function (LongWaitDetected $event) {
        Log::warning('Long queue wait', [
            'connection' => $event->connection,
            'queue'      => $event->queue,
            'seconds'    => $event->seconds,
        ]);
        // Push to your alerting system
    });

    Event::listen(JobFailed::class, function (JobFailed $event) {
        Metrics::increment('queue.job_failed', [
            'job' => class_basename($event->payload['displayName']),
        ]);
    });
}

```

`LongWaitDetected` fires when a queue's wait time exceeds the threshold set in `horizon.waits`. Configure it per queue:

```php
'waits' => [
    'redis:critical' => 5,   // alert after 5 seconds
    'redis:default'  => 60,
],

```

---

Takeaways
---------

- **Separate supervisors by latency contract** — never mix sub-second and multi-minute jobs in the same supervisor.
- **`auto` balance is not free** — tune `balanceMaxShift` and `balanceCooldown` to prevent worker thrashing.
- **Set `$failOnTimeout = true`** on long-running jobs so failures surface in the failed jobs table.
- **Memory limits are per-worker**, not per-supervisor — size them against your actual job footprint.
- **Hook `LongWaitDetected`** for proactive alerting instead of discovering queue backlogs after user complaints.
- **`retryUntil` beats `$tries`** for time-sensitive jobs where absolute deadline matters more than attempt count.

 Found this useful?

          [  ](https://twitter.com/intent/tweet?url=https%3A%2F%2Fmsaied.com%2Farticles%2Flaravel-horizon-deep-dive-queue-tuning-supervisor-strategies-and-job-reliability&text=Laravel+Horizon+Deep+Dive%3A+Queue+Tuning%2C+Supervisor+Strategies%2C+and+Job+Reliability) [  ](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fmsaied.com%2Farticles%2Flaravel-horizon-deep-dive-queue-tuning-supervisor-strategies-and-job-reliability) 

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

  3 questions  

     Q01  What is the difference between 'simple' and 'auto' balance modes in Horizon?        'simple' divides processes evenly across all queues in the supervisor list. 'auto' monitors queue depth in real time and shifts workers toward whichever queue has the most pending jobs, subject to balanceMaxShift and balanceCooldown limits. 

      Q02  How does Horizon handle a job that exceeds its timeout?        Horizon sends SIGKILL to the worker process — there is no exception thrown inside the job. Set `$failOnTimeout = true` (Laravel 10+) so the job is recorded as failed. Always design long-running jobs to be idempotent so a retry can safely resume. 

      Q03  Can I override the timeout at the job level rather than in the Horizon config?        Yes. Defining a public `$timeout` property on the job class overrides the supervisor-level timeout for that specific job. The lower of the two values wins, so ensure your supervisor timeout is at least as high as your longest job-level timeout. 

  Continue reading

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

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

 [ ![Laravel Pennant: Feature Flags with Scope, Storage, and Custom Drivers](https://cdn.msaied.com/212/ab98aa676ce445275d736755a046b360.png) laravel feature-flags pennant 

### Laravel Pennant: Feature Flags with Scope, Storage, and Custom Drivers

Laravel Pennant ships with more power than most teams use. Learn how to scope flags to tenants, swap storage d...

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

 16 Jun 2026     4 min read  

  Read    

 ](https://msaied.com/articles/laravel-pennant-feature-flags-with-scope-storage-and-custom-drivers) [ ![Laravel Eloquent Global Scopes: Pitfalls, Testing, and Composing Them Safely](https://cdn.msaied.com/211/8b9b19e7ecbf690b182ffbe6bffc9530.png) laravel eloquent testing 

### Laravel Eloquent Global Scopes: Pitfalls, Testing, and Composing Them Safely

Global scopes are powerful but easy to misuse. Learn how to write, test, and safely compose Eloquent global sc...

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

 16 Jun 2026     1 min read  

  Read    

 ](https://msaied.com/articles/laravel-eloquent-global-scopes-pitfalls-testing-and-composing-them-safely) [ ![Eloquent Custom Relations: Polymorphic Pivots, HasManyThrough Tricks, and Raw Join Relations](https://cdn.msaied.com/210/b47272214946c6adcd02ddf74b7df816.png) laravel eloquent database 

### Eloquent Custom Relations: Polymorphic Pivots, HasManyThrough Tricks, and Raw Join Relations

Beyond belongsTo and hasMany lies a set of underused Eloquent relation techniques. This guide covers custom re...

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

 16 Jun 2026     3 min read  

  Read    

 ](https://msaied.com/articles/eloquent-custom-relations-polymorphic-pivots-hasmanythrough-tricks-and-raw-join-relations) 

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