Laravel Horizon vs Queue Workers: Which One Should You Use?

Laravel Horizon vs Queue Workers: Which One Should You Use?

Laravel Horizon vs Queue Workers: Which One Should You Use?

Laravel gives you two main ways to process background jobs: the classic queue:work workers and the Horizon dashboard/manager for Redis queues. Both run the same jobs and use the same queue system—but they differ in operability, visibility, and auto-scaling features. In this guide, you’ll learn their trade-offs, when to pick one over the other, how to configure each in production, and how to migrate smoothly between them.

1 – The Short Answer

  • Use queue:work if you want maximum simplicity, minimal dependencies, or you’re not on Redis yet.
  • Use Horizon if you’re on Redis and need auto-balancing, per-queue scaling, live metrics, failure insights, and an operations-friendly dashboard.

If your app is growing or is already in production with meaningful throughput, Horizon usually pays for itself in visibility and control. For the fundamentals of queues, read Article #42 – Queues. For the Horizon dashboard and features, see Article #45 – Horizon.

2 – Classic Workers with queue:work

Classic workers are just long-running PHP processes that pull jobs from your configured backend (database, Redis, SQS, etc.). They’re easy to run and script with systemd or Supervisor.

# Run a worker on the default connection
php artisan queue:work --queue=default --sleep=1 --tries=3

# Run multiple workers for throughput
php artisan queue:work --queue=emails --sleep=1 --tries=3
php artisan queue:work --queue=reports --sleep=1 --tries=3Code language: Bash (bash)

The command pulls jobs from the specified queue list and processes them. Flags like --sleep control polling behavior; --tries sets the automatic retry count before failure.

; /etc/systemd/system/laravel-queue@.service
[Unit]
Description=Laravel Queue Worker for %i
After=network.target

[Service]
User=www-data
Restart=always
RestartSec=3
WorkingDirectory=/var/www/current
ExecStart=/usr/bin/php artisan queue:work --queue=%i --sleep=1 --tries=3
ExecStop=/bin/kill -s SIGTERM $MAINPID

[Install]
WantedBy=multi-user.targetCode language: TOML, also INI (ini)

This systemd template lets you run one service per queue (e.g., laravel-queue@emails, laravel-queue@reports). It automatically restarts workers, making basic operations simple without a dashboard.

3 – Horizon: Managed, Observable Redis Workers

Horizon is a layer on top of Redis queues that adds a web UI, auto-balancing, per-queue processes, tags, batches, and real-time metrics.

composer require laravel/horizon
php artisan horizon:install
php artisan migrate
php artisan horizonCode language: Bash (bash)

Installing Horizon publishes config and migrations for job monitoring. Running php artisan horizon starts the Horizon master, workers, and a dashboard available at /horizon. See our full walkthrough in Article #45.

// config/horizon.php (snippet)
'supervisors' => [
    'app-supervisor' => [
        'connection' => 'redis',
        'queue' => ['default', 'emails', 'reports'],
        'balance' => 'auto',
        'minProcesses' => 2,
        'maxProcesses' => 12,
        'tries' => 3,
    ],
],Code language: PHP (php)

Supervisors define how many worker processes run per queue group. With balance: auto, Horizon dynamically shifts processes to hot queues, improving throughput during spikes.

; /etc/systemd/system/horizon.service
[Unit]
Description=Laravel Horizon
After=network.target

[Service]
User=www-data
WorkingDirectory=/var/www/current
ExecStart=/usr/bin/php artisan horizon
Restart=always
RestartSec=3

[Install]
WantedBy=multi-user.targetCode language: TOML, also INI (ini)

Running Horizon under systemd makes it resilient across deploys and reboots. It will automatically reload workers when your code changes, reducing manual restarts compared to classic workers.

4 – Side-by-Side Comparison

  • Backends: queue:work supports Database/Redis/SQS/etc. Horizon focuses on Redis.
  • Visibility: Classic workers = logs only. Horizon = live dashboard (throughput, latency, failures, retries, tags, batches).
  • Scaling: Classic = manual process counts. Horizon = per-queue supervisors, min/maxProcesses, auto-balancing.
  • Ops: Classic = simplest, fewer moving parts. Horizon = more features, easier on-call debugging.
  • Cost: Horizon adds Redis dependency & instance size considerations, but saves ops time under load.
  • Security: Protect /horizon via auth/gates; in classic mode there’s no dashboard to secure.

For high-traffic environments, the observability and auto-scaling that Horizon brings typically outweigh the extra moving parts. Pair Horizon with Redis and caching strategies from Article #43 for best results.

5 – Job Tagging & Batches (Horizon Extras)

Horizon can group and filter jobs by tags and show batch progress. Tagging makes debugging easier during incidents.

// app/Jobs/SendWelcomeEmail.php (snippet)
public function tags(): array
{
    return ['user:'.$this->user->id, 'emails'];
}Code language: PHP (php)

These tags let you filter the Horizon dashboard to see only jobs for a specific user or category—handy for troubleshooting failures and replays.

// Dispatch a batch with progress tracking
use Illuminate\Bus\Batch;
use Illuminate\Support\Facades\Bus;

$batch = Bus::batch([
    new ImportRow($file, 1),
    new ImportRow($file, 2),
    // ...
])->then(fn (Batch $batch) => logger('Import complete'))
 ->catch(fn (Batch $batch, Throwable $e) => logger('Import failed'))
 ->name('Customer Import')
 ->dispatch();Code language: PHP (php)

Batches group multiple jobs and show progress/failures in Horizon. This is invaluable for long-running imports where you need visibility and retry control at scale.

6 – A Tiny Admin UI Toggle (Optional)

Here’s a minimal Blade UI to simulate switching strategies (note: in reality you enable one or the other at deploy time). It’s useful for documenting your operations runbook.

<!-- resources/views/admin/queues.blade.php -->
@extends('layouts.app')

@section('content')
<div class="container">
  <h1 class="mb-4">Queue Strategy</h1>

  <div class="card mb-3">
    <div class="card-body">
      <p class="mb-3">Current: <strong>Horizon</strong> (Redis)</p>
      <a href="/horizon" class="btn btn-theme r-04">Open Horizon Dashboard</a>
      <a href="{{ route('admin.docs.queue') }}" class="btn btn-outline-secondary ms-2">Operations Runbook</a>
    </div>
  </div>

  <p class="text-muted">Switching between Horizon and classic workers is usually done during deployment with systemd services.</p>
</div>
@endsectionCode language: HTML, XML (xml)

This page links operators to Horizon and your internal docs. Treat “switching” as a deployment concern (start/stop services), not a runtime toggle.

7 – Deployment & Reliability Notes

  • Supervise processes: Use systemd/Supervisor for both Horizon and classic workers so they auto-restart.
  • Environment parity: Keep dev/staging using the same backend (Redis) to catch scaling issues early.
  • Back-pressure: Use queue priorities (multiple queues) and Horizon supervisors to keep critical jobs flowing.
  • Observability: Pair Horizon with Telescope for request/query insights; they complement each other.
  • High concurrency: If request throughput is also high, consider Octane for the web tier and Horizon for workers.

For production checklists and CI, see Article #58 – Deployment Checklist and Article #54 – CI/CD.

8 – Migrating from Classic Workers to Horizon

  • Ensure QUEUE_CONNECTION=redis and Redis is sized properly.
  • Install/configure Horizon supervisors with sane min/maxProcesses.
  • Roll a canary: point a subset of queues to Horizon, watch metrics, then cut over fully.
  • Tag key jobs and use batches where visibility helps during the transition.
  • Decommission old queue:work services after verifying parity.

This staged approach reduces risk while giving you immediate operational benefits—especially the dashboard and auto-balancing during traffic spikes.

Wrapping Up

Classic workers are simple and effective for small to medium workloads or non-Redis backends. Horizon shines when you need operational visibility, auto-balancing, tags/batches, and a production-grade dashboard—all on Redis. Many teams start with classic workers and graduate to Horizon as load and complexity grow. Choose based on your current scale, team needs, and infrastructure.

What’s Next

0 Comments

Leave a Comment

Your email address will not be published. Required fields are marked *

Add Comment *

Name *

Email *

Keep Reading...

How to Schedule Jobs in Laravel with Task Scheduling
How to Schedule Jobs in Laravel with Task Scheduling

Laravel’s task scheduling system allows you to automate repetitive jobs such as clearing caches, sending out reports, or syncing data. Instead of…

How to Use Laravel Horizon for Queue Monitoring
How to Use Laravel Horizon for Queue Monitoring

How to Use Laravel Horizon for Queue Monitoring When your Laravel app uses queues to handle heavy workloads (emails, reports, notifications), monitoring…

How to Use Laravel Queues for Faster Performance
How to Use Laravel Queues for Faster Performance

How to Use Laravel Queues for Faster Performance When your Laravel app handles tasks like sending emails, generating reports, or processing uploads,…