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 vs Supervisor for Queue Workers

By Kokil Thapa | Last reviewed: August 2026

Choosing between Laravel Horizon vs Supervisor for queue workers determines whether your background jobs are observable, auto-scaling, and recoverable, or simply running as opaque processes. Many developers I work with on Laravel projects in Nepal start with plain Supervisor because it feels simpler, then migrate to Horizon once job volume grows or debugging becomes painful. This guide breaks down the architectural differences, real configuration patterns for Laravel 12 and PHP 8.4, and the specific trade-offs that matter in production environments.

What Is the Difference Between Laravel Horizon and Supervisor?

Supervisor is a Linux-level process control system. It knows nothing about Laravel, queues, or business logic. Its only job is to ensure that a specified number of processes (like php artisan queue:work) remain running, restarting them if they crash. It writes logs to files and has no web interface.

Laravel Horizon is an application-level package built specifically for Laravel’s Redis queue driver. It provides a real-time dashboard, configurable auto-scaling strategies, job tagging, failure tracking, and metrics. Crucially, Horizon itself runs under Supervisor. Horizon does not replace Supervisor; it replaces manual queue:work commands with intelligent worker pools managed through a configuration file.

Laravel Horizon vs Supervisor ArchitectureSupervisor Onlysupervisord↓ spawnsphp artisan queue:workHorizon + Supervisorsupervisord↓ spawnsphp artisan horizonCharacteristicsNo dashboard • Static workersFile logs • Manual scalingCharacteristicsRedis dashboard • Auto-scalingJob tags • Failure metricsHorizon requires Supervisor
Laravel Horizon vs Supervisor for queue workers: Horizon adds an application layer on top of Supervisor’s process management

In practice, the decision is rarely "one or the other." For any Laravel application using Redis queues in 2026, Supervisor is mandatory infrastructure. Horizon is the optional but highly recommended application layer that makes queue operations visible and manageable. If you are building high-traffic Laravel queue systems, skipping Horizon means accepting significant operational blind spots.

How Do You Configure Supervisor for Laravel Queue Workers?

Supervisor configuration lives in /etc/supervisor/conf.d/ on Ubuntu 22.04/24.04. Each Laravel application should have its own .conf file. Below is a production-tested configuration for Laravel 12 running on PHP 8.4:

[program:laravel-worker] process_name=%(program_name)s_%(process_num)02d command=php /var/www/myapp/current/artisan queue:work redis --sleep=3 --tries=3 --max-time=3600 --memory=256 autostart=true autorestart=true stopasgroup=true killasgroup=true user=www-data numprocs=4 redirect_stderr=true stdout_logfile=/var/www/myapp/shared/storage/logs/worker.log stopwaitsecs=3600

Key parameters explained from real deployment experience:

  • --max-time=3600: Forces each worker to restart after one hour. This prevents memory leaks from accumulating in long-running PHP processes. On legal-tech portals I maintain, this single flag eliminated weekly OOM kills.
  • --memory=256: Sets a hard memory ceiling per worker. Jobs exceeding this cause a graceful worker restart rather than silent failures.
  • --sleep=3: Seconds to wait before polling for new jobs when the queue is empty. Lower values increase Redis load; higher values add latency. Three seconds is a sensible default for most workloads.
  • --tries=3: Maximum attempts before marking a job as failed. Always pair this with exponential backoff in your job class.
  • stopwaitsecs=3600: Must be ≥ your longest expected job duration. If Supervisor kills a worker mid-job, you get duplicate processing or data corruption.

After creating the config file, always run:

sudo supervisorctl reread sudo supervisorctl update sudo supervisorctl start laravel-worker:*

A common mistake on shared hosting or budget VPS setups in Nepal is setting numprocs too high relative to available RAM. Each worker consumes 50–150 MB depending on application bootstrapping. Four workers at 256 MB limit plus OS overhead requires at least 2 GB dedicated RAM. Monitor with htop before increasing process counts.

When Should You Use Laravel Horizon Instead of Plain Supervisor?

Horizon justifies its additional complexity when your application meets any of these conditions:

  1. Multiple queue priorities: You have separate queues like emails, invoices, imports and need different worker counts or retry policies per queue.
  2. Traffic spikes are unpredictable: E-commerce flash sales, document processing bursts during Nepali fiscal year-end, or webhook floods from payment gateways like eSewa or Khalti require dynamic scaling.
  3. Debugging failed jobs is frequent: Your team spends time SSH-ing into servers to grep log files instead of using a searchable failure UI.
  4. You need throughput metrics: Business stakeholders ask "how many orders processed per minute?" or "what's the average email delivery time?"
  5. Zero-downtime deployments matter: Horizon’s horizon:terminate command gracefully finishes current jobs before deploying new code, preventing job loss during releases.

If your application processes fewer than 1,000 jobs per day with uniform priority and predictable load, plain Supervisor with static workers is perfectly adequate. Horizon adds value proportional to operational complexity, not merely job volume.

Decision: Horizon or Plain Supervisor?Start: Redis Queue App>1K jobs/day OR multiple queues?NOYESPlain SupervisorStatic workers, file logsAdd Horizon LayerAuto-scale, dashboard, tagsNeed spike handling or metrics?NOYESHorizon Basic Config SufficientFull Horizon Setup
Decision framework for Laravel Horizon vs Supervisor for queue workers based on traffic volume and operational needs

How Does Laravel Horizon Auto-Scaling Work in Production?

Horizon’s auto-scaling is configured in config/horizon.php under environment-specific arrays. Unlike Kubernetes HPA which scales pods, Horizon adjusts the number of worker processes within a single server based on queue backlog:

'environments' => [ 'production' => [ 'supervisor-1' => [ 'connection' => 'redis', 'queue' => ['default', 'emails', 'invoices'], 'balance' => 'auto', 'minProcesses' => 2, 'maxProcesses' => 12, 'maxTime' => 3600, 'maxJobs' => 1000, 'memory' => 256, 'tries' => 3, 'nice' => 0, ], ], ],

The balance => 'auto' strategy uses a cooldown algorithm. When pending jobs exceed available workers, Horizon spawns new processes up to maxProcesses. When the queue drains, it waits through a cooldown period before terminating excess workers. This prevents rapid spawn/kill cycles during traffic plateaus.

On a legal document processing platform I built, we set minProcesses=2 for baseline email notifications and maxProcesses=8 for bulk attestation uploads during peak government filing seasons. The key insight: maxProcesses must respect server RAM. Eight workers at 256 MB each plus Redis, Nginx, PHP-FPM, and MySQL requires minimum 4 GB RAM on a single EC2 instance. Always calculate before deploying.

For multi-server deployments, each server runs its own Horizon instance with identical config. Horizon coordinates via Redis locks, so two servers won’t double-process the same job. This is where Redis as a coordination layer proves essential beyond simple queue storage.

Laravel Horizon vs Supervisor: Feature Comparison Table

FeatureSupervisor OnlyHorizon + Supervisor
Process Management✅ Core function✅ Via Supervisor underneath
Web Dashboard❌ None✅ Real-time Vue.js UI
Auto-Scaling Workers❌ Static numprocs only✅ Balance strategies (auto/simple/false)
Failed Job Inspection❌ Log files only✅ Searchable UI with payload/retry
Job Tagging & Filtering❌ Not possible✅ Model-based automatic tags
Throughput Metrics❌ Manual log parsing✅ Per-queue jobs/minute, runtime
Graceful Deploy Termination⚠️ Manual SIGTERM handling✅ horizon:terminate built-in
Queue Driver Support✅ Any (database, sqs, redis)❌ Redis only
Setup ComplexityLow (~15 min)Medium (~45 min + Redis)
Server Resource OverheadNegligible+Redis memory, +PHP master process

The critical constraint: Horizon requires Redis. If your infrastructure uses database or SQS queues and migrating to Redis isn’t feasible, Supervisor alone is your only option. For new Laravel 12 projects in 2026, starting with Redis is strongly recommended regardless of current scale.

Worker Lifecycle: Supervisor vs HorizonPlain SupervisorHorizon ManagedFixed N workers started at bootMin workers started, monitor beginsPoll queue → Process → RepeatCheck backlog → Scale up if neededCrash? Restart same countQueue empty? Cooldown → Scale downDeploy: Manual restart requiredDeploy: horizon:terminate gracefulPredictable • Simple • BlindAdaptive • Observable • Complex
Worker lifecycle differences in Laravel Horizon vs Supervisor for queue workers: static vs adaptive process management

Common Production Pitfalls When Migrating to Horizon

Having migrated several production Laravel applications from plain Supervisor to Horizon, these issues recur consistently:

Forgetting to disable old Supervisor configs. After installing Horizon, you must remove or comment out your previous laravel-worker Supervisor program. Running both causes duplicate job processing. Always verify with supervisorctl status after migration.

Insufficient Redis memory. Horizon stores metrics, tags, and recent job payloads in Redis. A busy application can consume 500 MB+ beyond queue data. Set maxmemory and maxmemory-policy allkeys-lru in Redis config to prevent OOM crashes. On a 2 GB Redis instance, allocate at least 1 GB for Horizon metadata.

Misconfigured maxTime/maxJobs. Without these, Horizon workers run indefinitely. Memory fragmentation in PHP 8.4 accumulates silently over days. Always set maxTime=3600 and maxJobs=1000 as starting points, adjusting based on your job profiles.

Ignoring Horizon’s own Supervisor config. Horizon ships a recommended Supervisor template. Use it. The critical line is command=php /path/to/artisan horizon, not queue:work. Horizon’s master process manages child workers internally.

For teams managing CI/CD pipelines with automated deployments, integrate php artisan horizon:terminate into your deploy script’s post-release hook. This ensures zero-downtime deploys without orphaned workers processing stale code.

Making the Right Choice for Your Laravel Application

The verdict on Laravel Horizon vs Supervisor for queue workers is layered, not binary. Supervisor is non-negotiable infrastructure for any production Laravel queue system. Horizon is the operational upgrade you adopt when visibility, auto-scaling, and debugging efficiency justify the Redis dependency and configuration overhead. For new Laravel 12 projects targeting growth, install Horizon from day one. For existing stable systems processing modest volumes, plain Supervisor remains a valid, low-maintenance choice.

If you’re evaluating queue architecture for a Laravel application and need practical guidance tailored to your traffic patterns, infrastructure constraints, or Nepal-specific deployment considerations, reach out to discuss your project. I’ve configured both approaches across dozens of production systems and can help you avoid the pitfalls that only surface under real load.

Frequently Asked Questions

Supervisor manages processes while Horizon manages queue workload. Supervisor ensures PHP worker processes stay alive and restart on failure. Horizon sits on top of Supervisor to provide real-time monitoring, tag-based job tracking, throughput metrics, and dynamic scaling configuration for Redis-backed queues.

Yes. Horizon requires Supervisor in production to keep its master process running. Without Supervisor, a crashed Horizon process leaves all queue workers dead until manually restarted. Configure Supervisor to run php artisan horizon as a long-lived daemon with automatic restarts and stdout/stderr logging enabled.

No. Horizon strictly requires Redis as the queue driver. It uses Redis streams and sorted sets for metrics, tagging, and job tracking. If your infrastructure only supports MySQL or database queues, stick with plain Supervisor-managed workers. Migrating to Redis is mandatory before installing Horizon in any Laravel 12 application.

Horizon adds approximately 50–80MB overhead for its master process and metric collectors beyond standard worker memory. Each individual worker consumes similar RAM whether managed by Horizon or Supervisor directly. Budget an extra 100MB minimum on Ubuntu servers running Horizon alongside your existing worker pool to avoid OOM kills during peak load.

Choose plain Supervisor when using database queues, running on minimal VPS resources under 2GB RAM, or managing simple single-queue workloads without monitoring needs. Horizon adds complexity justified only by Redis usage, multiple concurrent queues, tag-based debugging requirements, or team visibility into job throughput. For small Nepal-based business sites with basic email queues, Supervisor alone suffices.

Create /etc/supervisor/conf.d/horizon.conf with command=php /var/www/html/artisan horizon, user=www-data, numprocs=1, autostart=true, autorestart=true, and stopsignal=TERM. Set stdout_logfile and stderr_logfile paths writable by www-data. Run sudo supervisorctl reread && sudo supervisorctl update then verify with supervisorctl status. Always test horizon:terminate signal handling before relying on zero-downtime deployments via Deployer 7.

This typically indicates Horizon cannot write to Redis or the dashboard route lacks authentication middleware. Verify REDIS_HOST and REDIS_PORT in .env match your running Redis instance. Check storage/logs/laravel.log for Redis connection exceptions. Ensure routes/horizon.php includes proper gate definitions restricting access. On shared EC2 instances I maintain, stale OPcache after deployment frequently causes this until php-fpm reloads properly.

Not dynamically. Horizon scales workers within predefined min/max bounds configured in config/horizon.php per supervisor group. You define thresholds like maxProcesses=10 and balanceMaxShift=1, but Horizon won't exceed these limits regardless of backlog size. True auto-scaling requires external tooling or Kubernetes HPA. For most Laravel applications I build, static bounds tuned to observed peak loads prevent resource exhaustion better than unbounded scaling.

First check failed_jobs table and Horizon's failed tab for exception details. Verify the specific queue connection and worker assignment in config/horizon.php matches where jobs were dispatched. Inspect Redis memory usage with redis-cli info memory since full Redis blocks all operations. Restart workers via php artisan horizon:pause then horizon:continue rather than killing processes. On production legal-tech portals I maintain, stuck jobs usually trace to missing dependencies or serialized model changes after deployment.

Horizon itself is safe but exposes sensitive job data requiring protection. Always restrict dashboard access using Horizon::auth() gate in AppServiceProvider, limiting to authenticated admins. Never expose /horizon publicly. Sanitize job payloads containing PII before dispatching. Enable HTTPS everywhere. In Nepal legal-tech projects handling court documents, I additionally encrypt sensitive attributes before queuing and purge completed job records aggressively via horizon.trim configuration to minimize data exposure windows.

Jobs continue processing during symlink swap because workers reference absolute release paths. However, code changes may cause failures if job classes changed mid-flight. Best practice: run php artisan horizon:pause before deploy, wait for active jobs to complete, swap releases, clear caches, then horizon:continue. Configure Deployer's post-deploy hook to handle this sequence automatically. On sister sites sharing my GitLab CI pipeline, this prevents class-not-found errors during zero-downtime releases.

Use php artisan horizon:status in cron or external uptime monitors to verify Horizon is running. Integrate with Laravel's health checks package to expose /up endpoints returning queue metrics. Configure alerts for failed job thresholds via horizon.failed event listeners. On client projects, I wire horizon:status output into simple bash scripts that trigger SMS notifications through local gateways when workers stop unexpectedly, catching issues before clients notice delayed emails or booking confirmations.

Yes. Define separate supervisor groups in config/horizon.php each targeting different Redis connections or prefixes. Each group maintains independent worker pools, balance strategies, and retry limits. This isolates critical transactional queues from bulk background processing. On eCommerce platforms like Nepal Gift Card, I separate order-processing queues from newsletter batches using distinct supervisor configurations, preventing marketing jobs from blocking time-sensitive payment webhooks during flash sales.

Horizon requires Redis 7.x or higher for full compatibility with Laravel 12. Older Redis versions lack stream commands and memory-efficient data structures Horizon depends on. Verify with redis-cli INFO SERVER before installation. On Ubuntu 24 servers, install via official Redis repository rather than default apt packages which often ship outdated versions. Always enable persistence and configure maxmemory-policy to volatile-lru to prevent Horizon metrics from evicting application cache during traffic spikes.

Plain Supervisor setup takes 1–2 hours (Rs 3,000–6,000, ~USD 22–45). Horizon implementation including Redis provisioning, configuration, dashboard security, and monitoring integration typically runs 6–10 hours (Rs 18,000–30,000, ~USD 135–225). Ongoing Redis hosting adds Rs 1,500–3,000 monthly (~USD 11–22) on managed services. For Nepali SMEs with simple notification queues, Supervisor suffices. Horizon justifies cost only when debugging visibility, multi-queue orchestration, or team collaboration demands outweigh infrastructure overhead.

Share this article

Quick Contact Options
Choose how you want to connect me: