
Table of Contents
By Khimananda Oli | Last reviewed: August 2026
Running background jobs without visibility is a liability in any production environment. Laravel Horizon: Monitor and Scale Your Queues effectively by combining real-time Redis metrics with intelligent supervisor configurations that adapt to actual workload demand. This guide covers the exact configuration patterns I use to keep job throughput stable and costs predictable, whether you are deploying to a single VPS or orchestrating containers via Docker for Laravel applications.
config/horizon.php using auto balancing with explicit minProcesses and maxProcesses limits. Pair this with dedicated Redis instances and OS-level process supervisors to ensure reliable job throughput under variable load.How does Laravel Horizon monitor and scale your queues automatically?
Horizon differs from standard Laravel queue workers because it acts as a process manager on top of Redis. Instead of running static php artisan queue:work commands, Horizon spawns and kills worker processes dynamically based on queue wait times. When you configure Laravel Horizon to monitor and scale your queues, you are essentially defining a feedback loop where the system measures backlog depth and adjusts compute resources within boundaries you set.
The core mechanism relies on three configuration keys in your supervisor definition:
- balance: Set to
autofor production. This tells Horizon to shift workers between queues based on current wait time ratios rather than fixed counts. - minProcesses / maxProcesses: Hard floors and ceilings. Never leave these unbounded in production; a sudden spike could exhaust server memory.
- balanceCooldown: The number of seconds Horizon waits before re-evaluating. A value of 3–10 seconds prevents process thrashing during bursty traffic.
What is the optimal Horizon configuration for production workloads?
A common mistake is copying development configs directly to production. In practice, you need environment-specific supervisor arrays. Below is a battle-tested configuration structure for a typical SaaS application handling emails, data imports, and report generation.
// config/horizon.php
'environments' => [
'production' => [
'supervisor-1' => [
'connection' => 'redis',
'queue' => ['high', 'default', 'low'],
'balance' => 'auto',
'minProcesses' => 4,
'maxProcesses' => 20,
'maxTime' => 3600,
'maxJobs' => 1000,
'memory' => 256,
'tries' => 3,
'timeout' => 300,
'nice' => 0,
],
'supervisor-imports' => [
'connection' => 'redis-long',
'queue' => ['imports'],
'balance' => 'simple',
'processes' => 3,
'memory' => 512,
'timeout' => 1800,
],
],
], Why separate long-running jobs?
Import jobs often take minutes and consume significant memory. If they share a supervisor with fast email jobs, they can block the pool or trigger OOM kills that disrupt high-priority traffic. Dedicated supervisors with simple balancing and higher memory limits isolate this risk. For teams managing infrastructure via code, defining these parameters in Terraform or Ansible ensures consistency across staging and production.
How do you tune Redis for reliable Laravel queue performance?
Horizon is only as fast as its Redis backend. When configuring Laravel Horizon to monitor and scale your queues, Redis tuning is non-negotiable. Default Redis settings often bottleneck at 5,000+ jobs per minute.
| Parameter | Default | Production Recommendation | Rationale |
|---|---|---|---|
maxmemory-policy | noeviction | volatile-lru | Prevents OOM crashes; evicts expiring keys first while preserving queue data |
tcp-backlog | 511 | 2048+ | Handles connection bursts during deployments or traffic spikes |
save | Multiple RDB snapshots | "" (disable) if using AOF | RDB forks cause latency spikes; rely on AOF fsync=everysec for durability |
hz | 10 | 100 | Faster expiration checks and timeout handling for queue workloads |
In Nepal’s growing tech ecosystem, many teams still run Redis on the same instance as their web server. This works for low traffic but fails under load. Dedicate at least a separate container or VM for Redis once you exceed 50 concurrent workers. If you are hosting on AWS, consider ElastiCache with cluster mode disabled for simpler Horizon compatibility, as documented in my guide on hosting Laravel on AWS EC2 and RDS.
How should you deploy and supervise Horizon in Docker or Kubernetes?
Horizon itself manages PHP worker processes, but something must manage the Horizon master process. In containerized environments, this distinction causes frequent failures when misconfigured.
- Use
execin your entrypoint: Your Dockerfile CMD should be["php", "artisan", "horizon"]or an entrypoint script that usesexec php artisan horizon. Withoutexec, SIGTERM signals from Kubernetes or Docker stop won’t reach Horizon, causing graceful shutdown failures and duplicate job processing. - Set termination grace period: Match your Kubernetes
terminationGracePeriodSecondsor Docker stop timeout to Horizon’s--timeoutplus buffer. If your longest job takes 300s, set grace period to 360s minimum. - Liveness probes: Use
php artisan horizon:statusor check the/horizon/api/statsendpoint. Do not probe individual worker PIDs; Horizon manages those internally. - Resource requests vs limits: Set memory requests to
maxProcesses × memory + 20%overhead. CPU requests should reflect average load, not peak, since Horizon scales workers up and down.
How do you debug stuck queues and scaling issues in Horizon?
Even with perfect configuration, queues stall. Here is the diagnostic sequence I follow when on-call:
- Check
horizon:status: Returnsrunning,paused, orinactive. If paused, someone ranhorizon:pauseduring deployment and forgot to resume. - Inspect recent failed jobs:
php artisan horizon:failedshows failures with context. Correlate timestamps with deployment logs or infrastructure events. - Verify Redis connectivity: Run
redis-cli -h <host> pingfrom the application container. Intermittent timeouts often indicate network saturation or Redis fork latency during BGSAVE. - Review supervisor balance logs: Horizon logs scaling decisions to
storage/logs/horizon.log. Look for rapid scale-up/scale-down cycles indicating an overly aggressivebalanceCooldown.
If jobs are processing but slower than expected, profile the job itself before blaming Horizon. Database locks, external API rate limits, and missing indexes are far more common culprits than queue misconfiguration. My article on Laravel performance optimization techniques covers profiling methods that apply directly to queued jobs.
Implementing Laravel Horizon to Monitor and Scale Your Queues Reliably
Getting Laravel Horizon to monitor and scale your queues reliably requires treating it as infrastructure, not just a package. Define supervisor strategies per environment, dedicate Redis resources, enforce graceful shutdown semantics in containers, and establish a clear debugging playbook before incidents occur. These practices have kept queue systems stable for me across AWS, bare-metal, and hybrid deployments serving both global and Nepali markets.
If your team needs help auditing your current queue architecture or designing a compliant, observable job processing pipeline, reach out to discuss your infrastructure. Whether you’re preparing for SOC 2 evidence collection or simply tired of 3 AM pager alerts, we can build a system that scales predictably and sleeps soundly.