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

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

Menu
----

Close 

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

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

 1. [Home](https://msaied.com/public)
2. /
3. [Articles](https://msaied.com/public/articles)
4. /
5. Laravel Horizon: Queue Metrics, Supervisor Tuning, and Safe Deployments

 Laravel Horizon: Queue Metrics, Supervisor Tuning, and Safe Deployments
========================================================================

 Beyond the dashboard: how to use Horizon's metrics API, tune supervisor processes for mixed workloads, and deploy without dropping jobs in production.

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

ShareCopy linkCopied

 ![Laravel Horizon: Queue Metrics, Supervisor Tuning, and Safe Deployments](https://cdn.msaied.com/660/4e7fb1097d66f5b5c6bb68e5aad9b211.png) 

  On this page +1. [Laravel Horizon in Production: Beyond the Pretty Dashboard](#laravel-horizon-in-production-beyond-the-pretty-dashboard)
2. [Querying the Metrics API](#querying-the-metrics-api)
3. [Supervisor Configuration for Mixed Workloads](#supervisor-configuration-for-mixed-workloads)
4. [Balance Strategies](#balance-strategies)
5. [Safe Deployments Without Dropping Jobs](#safe-deployments-without-dropping-jobs)
6. [Systemd Unit Example](#systemd-unit-example)
7. [Horizon in Docker / Kubernetes](#horizon-in-docker-kubernetes)
8. [Takeaways](#takeaways)

 Laravel Horizon in Production: Beyond the Pretty Dashboard
----------------------------------------------------------

Horizon ships with a slick UI, but the real value for a senior engineer is in its programmatic metrics API, fine-grained supervisor configuration, and the ability to drain workers cleanly during deployments. This article focuses on those three areas with concrete examples.

---

Querying the Metrics API
------------------------

Horizon stores throughput and runtime snapshots in Redis. You can read them directly via the `Horizon\Contracts\MetricsRepository` binding rather than scraping the UI.

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

$metrics = app(MetricsRepository::class);

// Throughput (jobs/min) for the last recorded snapshot
$throughput = $metrics->throughput();

// Runtime in milliseconds for a specific job class
$runtime = $metrics->runtimeForJob(ProcessInvoice::class);

// All measured queues
$queues = $metrics->measuredQueues();

```

This is useful for custom alerting: pipe `$throughput` into a Prometheus pushgateway or a Slack webhook when it drops below a threshold, without depending on a third-party APM.

---

Supervisor Configuration for Mixed Workloads
--------------------------------------------

A single supervisor with a high `maxProcesses` count is the most common Horizon misconfiguration. Different job classes have wildly different runtimes and resource profiles. Separate them.

```php
// config/horizon.php
'environments' => [
    'production' => [
        'supervisor-critical' => [
            'connection' => 'redis',
            'queue'      => ['critical'],
            'balance'    => 'auto',
            'minProcesses' => 2,
            'maxProcesses' => 10,
            'tries'      => 3,
            'timeout'    => 30,
        ],
        'supervisor-default' => [
            'connection' => 'redis',
            'queue'      => ['default'],
            'balance'    => 'simple',
            'processes'  => 4,
            'tries'      => 3,
            'timeout'    => 90,
        ],
        'supervisor-heavy' => [
            'connection' => 'redis',
            'queue'      => ['exports', 'reports'],
            'balance'    => 'simple',
            'processes'  => 2,
            'tries'      => 1,
            'timeout'    => 600,
        ],
    ],
],

```

### Balance Strategies

- **`simple`** — distributes processes evenly across queues in the list. Good when queue depths are predictable.
- **`auto`** — scales processes toward queues with the longest wait time. Use this for `critical` where latency matters.
- **`false`** — no balancing; each process works the queues in order. Rarely correct in production.

Set `balanceCooldown` (seconds) when using `auto` to prevent thrashing:

```php
'balanceCooldown' => 3,
'minProcesses'    => 1,
'maxProcesses'    => 15,

```

---

Safe Deployments Without Dropping Jobs
--------------------------------------

The naive `php artisan horizon:terminate` in a deploy script kills workers immediately, potentially mid-job. The correct sequence:

```bash
# 1. Tell Horizon to finish current jobs then exit
php artisan horizon:pause

# 2. Run your deployment steps (composer, migrations, asset build)
# ...

# 3. Restart Horizon — supervisor (systemd/Forge) brings it back
php artisan horizon:terminate

```

`horizon:pause` stops workers from picking up new jobs but lets in-flight jobs complete. `horizon:terminate` then sends `SIGTERM` to the master process, which waits for workers to finish before exiting.

### Systemd Unit Example

```ini
[Unit]
Description=Laravel Horizon
After=network.target

[Service]
User=forge
ExecStart=/usr/bin/php /var/www/app/artisan horizon
Restart=on-failure
RestartSec=5
KillSignal=SIGTERM
TimeoutStopSec=3600

[Install]
WantedBy=multi-user.target

```

Setting `TimeoutStopSec=3600` gives long-running export jobs up to an hour to finish before systemd force-kills the process. Tune this to your longest expected job runtime.

### Horizon in Docker / Kubernetes

In a container environment, trap `SIGTERM` and delegate to Horizon's own signal handler:

```dockerfile
CMD ["php", "artisan", "horizon"]

```

Kubernetes sends `SIGTERM` on pod termination. Horizon's master process catches it and begins a graceful shutdown. Set `terminationGracePeriodSeconds` in your pod spec to match `TimeoutStopSec`.

---

Takeaways
---------

- Use `MetricsRepository` directly for custom alerting instead of polling the UI.
- Separate supervisors by job profile (latency-sensitive, long-running, bulk) rather than one fat supervisor.
- Use `balance => auto` with a cooldown for variable-load queues; use `simple` for predictable ones.
- Always `horizon:pause` before deploying, then `horizon:terminate` after assets are in place.
- Match `TimeoutStopSec` / `terminationGracePeriodSeconds` to your longest job timeout to avoid mid-job kills.

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

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

  What is the difference between `horizon:terminate` and `horizon:pause`?`horizon:pause` stops workers from pulling new jobs but lets current jobs finish. `horizon:terminate` sends SIGTERM to the Horizon master process, which then waits for all workers to complete before exiting. Use pause first during deploys, then terminate after your code is in place.

   When should I use `balance =&gt; auto` vs `balance =&gt; simple`?Use `auto` when queue depths fluctuate and you want Horizon to shift workers toward the busiest queue automatically. Use `simple` when queue depths are predictable and you want an even split. Always set `balanceCooldown` with `auto` to prevent rapid process thrashing.

   How do I expose Horizon metrics to an external monitoring system?Inject `Laravel\\Horizon\\Contracts\\MetricsRepository` and call `throughput()` or `runtimeForJob()` inside a scheduled command. From there you can push values to Prometheus, Datadog, or any custom webhook without relying on the Horizon UI or a third-party package.

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

[Mohamed Said](https://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://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 articleCustom Eloquent Casts: Value Objects, Enums, and Encrypted Composites](https://msaied.com/public/articles/custom-eloquent-casts-value-objects-enums-and-encrypted-composites) [Next articleFilament v4 Table Bulk Actions: Custom Confirmation Modals and Scoped Authorization](https://msaied.com/public/articles/filament-v4-table-bulk-actions-custom-confirmation-modals-and-scoped-authorization)  

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

1. [Laravel Horizon in Production: Beyond the Pretty Dashboard](#laravel-horizon-in-production-beyond-the-pretty-dashboard)
2. [Querying the Metrics API](#querying-the-metrics-api)
3. [Supervisor Configuration for Mixed Workloads](#supervisor-configuration-for-mixed-workloads)
4. [Balance Strategies](#balance-strategies)
5. [Safe Deployments Without Dropping Jobs](#safe-deployments-without-dropping-jobs)
6. [Systemd Unit Example](#systemd-unit-example)
7. [Horizon in Docker / Kubernetes](#horizon-in-docker-kubernetes)
8. [Takeaways](#takeaways)

 ###  Have a technical challenge?

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

[Start a conversation](https://msaied.com/public#contact) 

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

 [ ![](https://cdn.msaied.com/745/8744e1be5136b430da52e9fca3ed3964.png)  · 3 min read### Service Container Deep Dive: Contextual Binding, Tagging, and Method Injection

6 Oct 2026 ](https://msaied.com/public/articles/service-container-deep-dive-contextual-binding-tagging-and-method-injection-1) [ ![](https://cdn.msaied.com/743/8998fac3a41451ab3fe1588194e17a43.png) Filament · 3 min read### Securing Filament Plugins with Plumb: Automated Security Scoring for PHP Packages

5 Oct 2026 ](https://msaied.com/public/articles/securing-filament-plugins-with-plumb-automated-security-scoring-for-php-packages) [ ![](https://cdn.msaied.com/742/2d02018669cdeedccb5de2efb898f0ee.png) Filament · 3 min read### Filament v3.3.56 Released: File Hash Names and Livewire Upload Fix

5 Oct 2026 ](https://msaied.com/public/articles/filament-v3356-released-file-hash-names-and-livewire-upload-fix) 

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

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

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