
August 12, 2026
10 min read
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.
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:
- Multiple queue priorities — Separate queues like
emails,invoices, andimportsneed different worker counts or retry rules. - Traffic spikes are unpredictable — Flash sales, fiscal-year document bursts, or webhook floods from Khalti and eSewa need dynamic scaling.
- Failed jobs need fast debugging — Your team should not SSH in to grep log files for every stuck payment callback.
- Stakeholders want throughput metrics — Questions like "orders per minute" or "average email delay" need a dashboard, not log parsing.
- Zero-downtime deploys matter —
horizon:terminatefinishes 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.
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?
| Feature | Supervisor Only | Horizon + 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 and retry |
| Job tagging and filtering | Not possible | Model-based automatic tags |
| Throughput metrics | Manual log parsing | Per-queue jobs per minute and runtime |
| Graceful deploy termination | Manual SIGTERM handling | horizon:terminate built in |
| Queue driver support | Any: database, SQS, Redis | Redis only |
| Setup complexity | Low, roughly 15 minutes | Medium, roughly 45 minutes plus Redis |
| Resource overhead | Negligible | Extra 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.
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.
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 matchingstopwaitsecson Supervisor configs to prevent silent OOM kills. - Remove old
queue:workSupervisor 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
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.

