Kokil Thapa - Professional Web Developer in Nepal
Freelancer Web Developer in Nepal with 15+ Years of Experience

Kokil Thapa is an experienced full-stack web developer focused on building fast, secure, and scalable web applications. He helps businesses and individuals create SEO-friendly, user-focused digital platforms designed for long-term growth.

Laravel Horizon: Monitor and Scale Your Queues

By Kokil Thapa | Last reviewed: August 2026

If you are running background jobs in a production PHP application, you need visibility into what is actually processing, failing, or stuck. Laravel Horizon: Monitor and Scale Your Queues provides the dashboard and configuration layer necessary to manage Redis-backed workers without guessing at process counts or retry thresholds. For developers building high-traffic systems, understanding this tool is as critical as knowing Laravel API best practices for request handling. This guide covers the exact configuration, infrastructure requirements, and scaling strategies I use on client projects to keep job throughput stable under load.

Why Use Laravel Horizon to Monitor and Scale Your Queues?

Standard Laravel queue workers run blindly. You can start ten workers via Supervisor, but you have no native insight into whether three are idle while seven are choking on a slow export job. Horizon solves this opacity by sitting between your application and Redis, tracking metrics like throughput, wait times, and failure rates in real time.

In my experience working on production Laravel applications, particularly legal-tech portals and eCommerce platforms, the moment you move beyond simple email queues to complex document generation or payment reconciliation, static worker counts fail. You either over-provision servers (wasting money) or under-provision them (causing backlogs). Horizon allows you to define scaling logic in code rather than server config.

Laravel AppDispatches JobsRedis ServerQueue StorageMetrics & TagsJob PayloadsHorizon WorkersAuto-ScaledHorizon DashboardReal-time Metrics
Laravel Horizon architecture: Application dispatches jobs to Redis, Horizon workers consume and auto-scale, dashboard reads metrics directly from Redis.

Unlike basic queue monitoring tools, Horizon persists metrics in Redis itself, meaning your dashboard survives application restarts and reflects historical trends. When you need to explain to a stakeholder why invoice generation slowed down during peak hours, Horizon’s charts provide the evidence. For teams managing multiple environments, having environment-specific scaling configurations committed to version control eliminates the "it works on staging" problem entirely.

How Do You Install and Configure Laravel Horizon for Production?

Installation is straightforward, but production configuration requires attention to detail. Horizon requires Redis as your queue driver; it does not support database, SQS, or Beanstalkd drivers for its monitoring features. Ensure your QUEUE_CONNECTION is set to redis in your .env file before proceeding.

Installation Steps

  1. Install via Composer:
    composer require laravel/horizon
  2. Publish assets and configuration:
    php artisan horizon:install
  3. Configure authorization in app/Providers/HorizonServiceProvider.php. Never leave the gate open in production. A typical check verifies admin status or specific permissions.
  4. Run migrations if you intend to store long-term metrics (optional but recommended for audit trails):
    php artisan migrate

Environment-Specific Configuration

The published config/horizon.php file contains an environments array. This is where you define how many workers run in production versus local development. On a real client project, I typically set conservative defaults and tune upward based on observed throughput.

'environments' => [
    'production' => [
        'supervisor-1' => [
            'connection' => 'redis',
            'queue' => ['default', 'high', 'low'],
            'balance' => 'auto',
            'maxProcesses' => 20,
            'memory' => 256,
            'tries' => 3,
            'timeout' => 300,
            'nice' => 0,
        ],
    ],
    'local' => [
        'supervisor-1' => [
            'maxProcesses' => 3,
        ],
    ],
],

Note the memory limit. Set this below your PHP CLI memory limit to prevent OOM kills. If your jobs process large files or datasets, profile them first. I’ve seen document generation jobs in legal-tech portals consume 400MB+; setting Horizon’s limit to 256MB caused silent failures until we adjusted both PHP and Horizon configs.

What Balancing Strategy Should You Use for Queue Workers?

Horizon offers three balancing modes: simple, false, and auto. Choosing correctly determines whether your system adapts to load or wastes resources.

StrategyBehaviorBest ForTrade-offs
simpleFixed number of workers per queuePredictable, uniform workloadsNo adaptation to spikes; manual tuning required
falseDisables Horizon worker managementUsing external orchestrators (Kubernetes)Loses auto-scaling; must manage processes externally
autoDynamically adjusts workers based on queue lengthVariable traffic, mixed job prioritiesSlightly higher Redis overhead; requires tuning min/max

For most production Laravel applications, auto is the correct default. It scales workers up when queues back up and down when they clear, within the bounds of minProcesses and maxProcesses. The key parameter often overlooked is balanceCooldown, which prevents rapid flapping when queue lengths oscillate. Set this to 3–5 seconds in production to stabilize worker counts.

Simple BalancingWorker 1Worker 2IdleIdleFixed count regardless of loadAuto BalancingWorker 1Worker 2Worker 3Worker 4Scales up during backlog, down when clearRecommendationUse 'auto' for variable workloads (eCommerce, legal portals, APIs)Use 'simple' only for predictable batch processing with known throughputAlways set balanceCooldown (3-5s) to prevent worker flapping
Simple balancing maintains fixed workers; auto balancing dynamically adjusts based on queue depth. Auto is preferred for most production Laravel applications.

When configuring auto mode, set minProcesses to handle your baseline load and maxProcesses to match your server’s capacity. A common mistake is setting maxProcesses too high relative to available RAM. Each worker consumes memory; twenty workers at 256MB each requires 5GB just for PHP processes, excluding Redis and Nginx. Always calculate total memory headroom before increasing concurrency.

How Do You Handle Failed Jobs and Retries Safely?

Horizon’s failed job dashboard is invaluable, but safe retry handling requires configuration beyond the UI. Define tries and timeout per supervisor in your config, not globally. Different job types have different tolerance for retries and execution time.

For payment webhook processing on eCommerce sites, I typically set tries to 5 with exponential backoff. For PDF generation in legal-tech portals, tries might be 2 with a longer timeout. Configure this by defining multiple supervisors targeting specific queues:

'supervisor-payments' => [
    'queue' => ['payments'],
    'tries' => 5,
    'backoff' => [60, 300, 900, 3600, 7200],
    'timeout' => 120,
],
'supervisor-documents' => [
    'queue' => ['documents'],
    'tries' => 2,
    'timeout' => 600,
],

Always implement the failed() method on jobs that trigger side effects. If a payment notification fails after all retries, log it to a dedicated channel or notify an admin. Silent failures in financial workflows are unacceptable. Horizon captures the exception, but business logic for escalation belongs in the job class itself.

What Infrastructure Does Laravel Horizon Require in Production?

Horizon adds operational requirements beyond standard Laravel. Understanding these prevents deployment surprises, especially when working with teams familiar with Laravel development in Nepal where shared hosting constraints sometimes influence architecture decisions.

  • Redis is mandatory. Horizon stores all metrics, tags, and job state in Redis. Use Redis 7.x or later; older versions lack some stream commands Horizon relies on. Dedicated Redis instances are preferred over shared caches to avoid metric loss during cache flushes.
  • Supervisor or systemd is still required. Horizon manages worker processes, but something must manage the Horizon master process itself. Use Supervisor or systemd to ensure horizon:work restarts on failure. Never run it manually in production.
  • PHP extensions. Ensure pcntl, posix, and redis extensions are installed and enabled. Missing pcntl causes silent failures in process management.
  • Deployment integration. Run php artisan horizon:terminate after every deployment. This signals Horizon to gracefully restart workers, picking up new code without dropping in-flight jobs. Add this to your Deployer or CI/CD pipeline post-deploy hook.
Deploy New CodeGit pull + installCache rebuildhorizon:terminateGraceful shutdownFinish current jobsSupervisor RestartsNew Horizon masterFresh workers spawnReadyProcessing jobsZero downtimeCritical: Never Skip horizon:terminateWithout graceful termination, workers continue running OLD codeJobs dispatched with NEW signatures will FAIL silentlyAdd to Deployer: run('php artisan horizon:terminate')Supervisor automatically respawns with updated codebase
Production deployment sequence: terminate Horizon gracefully after deploy to ensure workers reload with new code. Skipping this step causes silent job failures.

On sister sites sharing a Deployer 7 + GitLab CI pipeline, the horizon:terminate command runs in the post-deploy task. This ensures every release picks up code changes without manual intervention. If you’re using zero-downtime deployments with symlinked releases, verify that the terminate command targets the correct PHP binary and path; stale symlinks in cron or deploy scripts are a recurring production issue I’ve debugged more than once.

How Do You Optimize Laravel Horizon Performance Under Load?

Monitoring itself has cost. Horizon writes metrics to Redis on every job lifecycle event. Under extreme throughput (thousands of jobs per minute), this can saturate Redis connections or increase latency for application reads.

Mitigate this by tuning metrics.trim_recent and metrics.trim_snapshots in your config. Reduce snapshot frequency from the default 5 minutes to 15 or 30 minutes if you don’t need granular historical data. Trim recent job records aggressively; keeping 24 hours of individual job metrics is rarely necessary for operational debugging.

Tag jobs strategically. Horizon’s tag-based filtering is powerful for tracing user-specific or tenant-specific workflows, but excessive tagging increases Redis write volume. Tag by business entity (order ID, user ID, document reference) rather than generic labels. In legal-tech portals, tagging by case reference allows support staff to trace all jobs related to a specific matter without querying logs.

Finally, separate your monitoring Redis instance from your application cache if possible. Cache flushes should never wipe queue metrics. On projects where budget constrains infrastructure, use Redis databases (SELECT 0 for cache, SELECT 1 for queues/metrics) as a minimum isolation boundary. For clients needing guidance on infrastructure costs, understanding website development cost in Nepal helps frame trade-offs between dedicated Redis instances and shared-resource architectures.

Implementing Laravel Horizon to Monitor and Scale Your Queues Effectively

Laravel Horizon transforms queue management from opaque guesswork into observable, tunable infrastructure. By configuring environment-specific scaling, choosing the right balancing strategy, handling failures explicitly, and respecting deployment hygiene, you build systems that adapt to real workload patterns rather than static assumptions. The investment in proper Horizon setup pays dividends every time traffic spikes or a third-party API slows down.

If you’re implementing Laravel Horizon to monitor and scale your queues on a production system and need hands-on configuration, performance auditing, or deployment pipeline integration, get in touch. I work with teams worldwide and have specific experience tuning queue infrastructure for legal-tech, eCommerce, and API-heavy Laravel applications.

Frequently Asked Questions

Laravel Horizon is a Redis-backed queue monitoring dashboard and configuration system for Laravel applications. It provides real-time visibility into job throughput, failures, and wait times while allowing code-based scaling configuration per environment.

No. Horizon requires Redis as the queue driver because it depends on Redis data structures for real-time metrics, tags, and job tracking. Database, SQS, and Beanstalkd drivers are unsupported. Use Laravel Telescope or custom logging for non-Redis queues.

Horizon is free open-source software under the MIT license. Production costs are infrastructure only: a managed Redis instance typically runs Rs 1,500–4,000 monthly (~USD 11–30) for small-to-medium Laravel apps in Nepal, plus server RAM for workers.

Horizon 5.x supports Laravel 11 and 12 with PHP 8.2 or higher. PHP 8.4 is fully supported. Node.js 22 LTS or 20 LTS is required to compile Horizon's frontend assets during deployment. Always check composer.json for exact constraints before upgrading.

Define environment-specific supervisor configurations in config/horizon.php using the environments array. Set minProcesses, maxProcesses, balanceMaxShift, and balanceCooldown per environment. Production might run 10–20 workers with auto-balancing, while staging uses 2–4. Deploy configuration changes via Deployer or CI/CD and reload supervisors with php artisan horizon:terminate to apply without downtime.

Pending jobs usually indicate no active workers or misconfigured supervisor processes. Run php artisan horizon:status to verify the master supervisor is running. Check Redis connectivity, ensure horizon:work processes are spawned, and confirm queue names in your job dispatch match configured supervisor queues. On production servers, also verify PHP-FPM or systemd hasn't killed worker processes due to memory limits.

Telescope provides general application debugging including occasional queue inspection, but lacks real-time throughput metrics, wait time tracking, or auto-scaling configuration. Horizon is purpose-built for queue operations with dedicated dashboards, tag-based filtering, failure rate alerts, and environment-aware process management. Use both together: Telescope for request-level debugging, Horizon for queue infrastructure monitoring.

Yes. Define a gate in HorizonServiceProvider::gate() using Auth::check() and role verification. In production, always protect the /horizon route behind authentication. For legal-tech portals or client systems I've built, I typically restrict access to admin users with specific permissions using Spatie Laravel Permission rather than exposing metrics publicly. Never leave Horizon open on production domains.

Horizon tracks failed jobs automatically with retry counts and exception details visible in the dashboard. Configure retry limits and backoff strategies in job classes using $tries and $backoff properties. Set up Slack or email notifications via Horizon::tag() callbacks for critical failures. On production systems, implement exponential backoff to prevent overwhelming external APIs during outages, and review failed job patterns weekly to identify systemic issues.

Horizon stores job payloads, metrics, and tags in Redis. Set maxmemory-policy to volatile-lru or allkeys-lru to evict old metrics when memory fills. Allocate at least 256MB for small apps, 1–2GB for high-throughput systems. Monitor memory usage via redis-cli INFO MEMORY. On shared hosting in Nepal, ensure your Redis plan allows sufficient memory; constrained instances cause silent metric loss and dashboard gaps.

Include php artisan horizon:terminate in your Deployer deploy.php after symlink swap to gracefully restart workers with new code. Workers finish current jobs before exiting, and supervisord respawns them with updated configuration. Ensure shared/storage and .env persist across releases. Frontend assets should be pre-compiled locally or in CI since production servers often lack Node.js. This pattern works reliably across multiple sister sites I maintain on shared EC2 infrastructure.

Stale metrics typically result from Horizon workers not running, Redis connection failures, or timezone mismatches between app and Redis. Verify workers are active via php artisan horizon:status. Check Redis logs for eviction warnings. Ensure APP_TIMEZONE matches your server and Redis configuration. Clear cached config with php artisan config:clear after environment changes. If metrics remain incomplete, inspect Horizon's own log channel for silent errors during metric aggregation.

Tune maxProcesses based on available CPU cores and job complexity. Use balance => auto with appropriate balanceMaxShift to scale workers dynamically. Partition heavy jobs across dedicated queues with separate supervisor configurations. Enable job batching for related tasks. Reduce payload size by storing large data in database or S3 rather than serializing into Redis. Profile slow jobs individually; Horizon shows runtime per job type, helping identify bottlenecks that benefit from optimization or async offloading.

Yes. Configure Horizon::slowJobsUsing() and Horizon::longWaitJobsUsing() callbacks in HorizonServiceProvider to trigger notifications when jobs exceed duration or wait time limits. Integrate with Slack, Discord, or SMS gateways common in Nepal like eSewa alerts or local SMS providers. Set conservative thresholds initially to avoid alert fatigue, then refine based on actual SLA requirements. This proactive monitoring catches degradation before users report problems.

Most issues stem from forgetting php artisan horizon:terminate after deploys, causing workers to run stale code. Misconfigured supervisor queue lists silently drop jobs dispatched to unlisted queues. Running Horizon without Redis persistence risks metric loss on restart. Exposing /horizon without authentication creates security vulnerabilities. Insufficient Redis memory causes silent data eviction. Always test Horizon configuration in staging first, verify worker status post-deploy, and monitor Redis health alongside application metrics.

Share this article

Quick Contact Options
Choose how you want to connect me: