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: September 2026

Choosing between Laravel Horizon vs Supervisor for queue workers decides whether your background jobs stay observable under load or run as silent PHP processes. Many teams I work with on Laravel projects in Nepal start with plain Supervisor, then add Horizon once job volume, failed-job debugging, or deploy safety becomes painful. This guide covers architecture, production configs for Laravel 13 and PHP 8.3+, and the trade-offs that actually matter on a VPS.

What Is the Difference Between Laravel Horizon and Supervisor?

Supervisor is a Linux process manager. It knows nothing about Laravel, queues, or your business rules. Its job is simple: keep a defined number of processes running and restart them after a crash. Logs go to files. There is no web UI.

Laravel Horizon is an application package built for Laravel's Redis queue driver. It adds a real-time dashboard, auto-scaling worker pools, job tags, failure tracking, and throughput metrics. Horizon does not replace Supervisor. Supervisor spawns php artisan horizon. Horizon then manages child workers internally.

Think of it as two layers. Supervisor handles OS-level reliability. Horizon handles queue-level intelligence. If you are building high-traffic Laravel queue systems, skipping Horizon means accepting blind spots you will feel during the first production incident.

Horizon vs Supervisor LayersSupervisor Onlysupervisordspawns queue:workStatic worker countHorizon + Supervisorsupervisordspawns horizonDynamic worker poolsRequires bothSupervisor TraitsFile logs onlyAny queue driverManual scalingHorizon TraitsWeb dashboardRedis driver onlyAuto-scaling
Laravel Horizon vs Supervisor for queue workers: Horizon sits above Supervisor as an application-level queue manager

The official Laravel Horizon documentation states this clearly. Horizon is a dashboard and configuration system for Redis queues. Supervisor remains the recommended way to keep the Horizon master process alive in production.

How Do You Configure Supervisor for Laravel Queue Workers?

Supervisor config files live in /etc/supervisor/conf.d/ on Ubuntu 22.04 and 24.04. Each Laravel app gets its own .conf file. Below is a production-tested setup for Laravel 13 on PHP 8.3 or 8.5:

[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 flags from real deployments:

  • --max-time=3600 — Restarts each worker after one hour. This stops PHP memory leaks from killing the server silently.
  • --memory=256 — Hard ceiling per worker. Exceeding it triggers a graceful restart instead of an OOM kill.
  • --sleep=3 — Poll interval when the queue is empty. Lower values increase Redis load. Higher values add latency.
  • --tries=3 — Max attempts before a job lands in the failed table. Pair with backoff in your job class.
  • stopwaitsecs=3600 — Must exceed your longest job runtime. Killing mid-job causes duplicates or partial writes.

After saving the config, reload Supervisor:

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

A common mistake on budget VPS setups is setting numprocs too high for available RAM. Each worker uses 50–150 MB after bootstrapping. Four workers at a 256 MB limit plus OS overhead needs at least 2 GB RAM. Check with htop before scaling up. For server tuning beyond queues, see our Linux system administration service or the Ubuntu server setup guide for PHP apps.

The Supervisor configuration reference documents every directive. In practice, stopasgroup and killasgroup matter most for Laravel. They ensure child processes die cleanly when Supervisor stops a worker.

When Should You Use Laravel Horizon Instead of Plain Supervisor?

Horizon earns its setup cost when your app hits any of these conditions:

  1. Multiple queue priorities — Separate queues like emails, invoices, and imports need different worker counts or retry rules.
  2. Traffic spikes are unpredictable — Flash sales, fiscal-year document bursts, or webhook floods from Khalti and eSewa need dynamic scaling.
  3. Failed jobs need fast debugging — Your team should not SSH in to grep log files for every stuck payment callback.
  4. Stakeholders want throughput metrics — Questions like "orders per minute" or "average email delay" need a dashboard, not log parsing.
  5. Zero-downtime deploys matterhorizon:terminate finishes current jobs before new code loads. This pairs well with Deployer zero-downtime releases.

If you process fewer than 1,000 jobs per day with one queue and steady load, plain Supervisor is fine. Horizon adds value with operational complexity, not raw job count alone. On booking platforms like Adventure Third Pole Trek, mixed queue priorities pushed us toward Horizon early.

Horizon or Plain Supervisor?Redis queue Laravel appOver 1K jobs/day?NOYESPlain SupervisorStatic workersAdd HorizonDashboard + scaleNeed metrics?Basic Horizon config
Decision framework for Laravel Horizon vs Supervisor for queue workers based on volume and ops needs

How Does Laravel Horizon Auto-Scaling Work in Production?

Horizon auto-scaling lives in config/horizon.php under environment-specific supervisor arrays. It adjusts worker process counts on a single server based on queue backlog. It does not replace horizontal scaling across multiple machines.

'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,
        ],
    ],
],

With balance => 'auto', Horizon spawns workers when pending jobs exceed capacity. It waits through a cooldown before killing excess workers when the queue drains. This prevents rapid spawn-kill loops during traffic plateaus.

On a legal-tech portal I built, we set minProcesses=2 for email alerts and maxProcesses=8 for bulk document uploads during peak filing seasons. Eight workers at 256 MB each plus Redis, Nginx, PHP-FPM, and MySQL needs at least 4 GB RAM on one VPS. Calculate before you deploy.

For multi-server setups, each server runs its own Horizon instance with identical config. Horizon coordinates through Redis locks. Two servers will not double-process the same job. This is where Redis as a coordination layer matters beyond simple queue storage. See also the dedicated Laravel Redis queue production guide and Horizon monitoring walkthrough.

What Does a Feature Comparison Look Like for Horizon vs Supervisor?

FeatureSupervisor OnlyHorizon + Supervisor
Process managementCore functionVia Supervisor underneath
Web dashboardNoneReal-time Vue.js UI
Auto-scaling workersStatic numprocs onlyBalance strategies: auto, simple, false
Failed job inspectionLog files onlySearchable UI with payload and retry
Job tagging and filteringNot possibleModel-based automatic tags
Throughput metricsManual log parsingPer-queue jobs per minute and runtime
Graceful deploy terminationManual SIGTERM handlinghorizon:terminate built in
Queue driver supportAny: database, SQS, RedisRedis only
Setup complexityLow, roughly 15 minutesMedium, roughly 45 minutes plus Redis
Resource overheadNegligibleExtra Redis memory and PHP master process

The hard constraint: Horizon requires Redis. If you run database or SQS queues and cannot migrate, Supervisor alone is your path. For new Laravel 13 projects in 2026, start with Redis regardless of current scale. It unlocks Horizon, caching, and session storage from one service. Our Redis caching guide for Laravel covers shared infrastructure patterns.

Worker Lifecycle ComparedPlain SupervisorHorizon ManagedFixed N workers at bootMin workers, monitor startsPoll, process, repeatScale up on backlogCrash: restart same countEmpty queue: scale downDeploy: manual restartDeploy: horizon:terminate
Laravel Horizon vs Supervisor for queue workers: static versus adaptive worker lifecycles in production

What Are Common Production Pitfalls When Migrating to Horizon?

These issues recur on every Horizon migration I have handled:

Leaving old Supervisor configs active. After installing Horizon, remove or comment out your previous laravel-worker program. Running both causes duplicate job processing. Verify with supervisorctl status after migration.

Under-provisioning Redis memory. Horizon stores metrics, tags, and recent payloads in Redis. Busy apps can consume 500 MB beyond queue data. Set maxmemory and maxmemory-policy allkeys-lru in Redis config. The Redis memory optimization docs explain eviction policies in detail.

Skipping maxTime and maxJobs. Without these, workers run indefinitely. PHP 8.4 and 8.5 memory fragmentation accumulates over days. Start with maxTime=3600 and maxJobs=1000, then tune from your job profiles.

Using queue:work in the Horizon Supervisor block. Horizon ships a recommended Supervisor template. The command must be php artisan horizon, not queue:work. The master process manages child workers internally.

For teams running CI/CD pipelines with automated deployments, add php artisan horizon:terminate to your post-release hook. This pairs with the Laravel production deployment checklist and GitLab CI deploy workflows. Bulk job patterns in Laravel job batching also benefit from Horizon's visibility during large imports.

Zero-Downtime Horizon DeployGit PushCI triggeredBuildComposer + assetsSymlinkNew release liveTerminatehorizon:terminateWhat Happens During TerminateCurrent jobs finish on old codeNo new jobs picked upSupervisor restarts horizon masterNew workers load fresh codeZero duplicate processing
Graceful deploy flow for Laravel Horizon vs Supervisor for queue workers using horizon:terminate

When debugging failed job payloads locally, a JSON formatter tool saves time parsing Horizon's serialized data in the dashboard. For ongoing ops after launch, our support and maintenance service covers queue monitoring and incident response.

Key Takeaways

  • Supervisor is mandatory infrastructure; Horizon is an optional Redis queue layer that adds visibility and auto-scaling.
  • Plain Supervisor fits low-volume apps with one queue, predictable load, and no need for a dashboard.
  • Horizon pays off with multiple queues, traffic spikes, frequent failed-job debugging, and zero-downtime deploys.
  • Always set --max-time, --memory, and matching stopwaitsecs on Supervisor configs to prevent silent OOM kills.
  • Remove old queue:work Supervisor programs after migrating to Horizon to avoid duplicate processing.
  • Size Redis memory for Horizon metadata separately from queue payload storage before going live.

People Also Ask

Can Laravel Horizon replace Supervisor entirely?

No. Horizon is a PHP application process. If it crashes or the server reboots, something must restart it. Supervisor fills that role. Horizon manages workers inside Laravel. Supervisor keeps Horizon alive at the OS level.

Does Horizon work with database or SQS queues?

Horizon supports Redis only. Database and Amazon SQS queues still need plain Supervisor with queue:work. If you cannot run Redis, Supervisor alone is the correct choice.

How many workers should I run per server?

Start with two workers per queue supervisor and a maxProcesses cap at roughly half your available RAM divided by per-worker memory limit. Monitor queue wait time and CPU. Scale maxProcesses up only after confirming headroom with htop and Redis memory stats.

What happens to running jobs during a deploy?

With plain Supervisor, a restart mid-job can cause duplicates or partial writes unless you handle signals manually. With Horizon, php artisan horizon:terminate tells workers to finish current jobs, then exit cleanly before Supervisor spawns a fresh Horizon master on the new release.

Choose the Right Queue Stack for Your Laravel App

The verdict on Laravel Horizon vs Supervisor for queue workers is layered, not binary. Supervisor is non-negotiable for any production Laravel queue. Horizon is the upgrade you add when visibility, auto-scaling, and deploy safety justify the Redis dependency. For new Laravel 13 projects targeting growth, install Horizon from day one. For stable systems under 1,000 jobs per day, plain Supervisor remains a valid low-maintenance choice.

Need help sizing workers, configuring Redis, or wiring Horizon into your deploy pipeline? Contact us to discuss your Laravel queue architecture. You can also reach out directly about your project. I have configured both approaches across dozens of production systems and can help you skip the failures that only show up 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

0 Comments

Leave a comment

Your email is not published. Comments appear once they have been read. Sign in to have your details filled in.

Quick Contact Options
Choose how you want to connect me: