laravelmastery
SnippetsArticlesSeriesNews

Queues in production · Lesson 3 of 3

Queues in production · Lesson 3

Horizon in production

Run it for realQueues

handle() can now run twice and leave order 1042 paid once. Horizon is the dashboard and the process supervisor for a Redis queue. It shows how long jobs wait, which ones failed, and how many workers are busy. It is not a second queue worker you run beside queue:work.

Video

01 One supervisor, and terminate it on deploy

Configure the production supervisor with the queues it may touch, a timeout, and a cap on processes. auto balance adds workers when a queue is deep and removes them when it is quiet.

PHP
return [
'production' => [
    'supervisor-1' => [
        'connection' => 'redis',
        'queue' => ['payments', 'default', 'mail'],
        'balance' => 'auto',
        'minProcesses' => 1,
        'maxProcesses' => 10,
        'tries' => 3,
        'timeout' => 60,
    ],
],

];

payments is the queue onQueue('payments') chose in the job class lesson. A supervisor that omits it never runs the order.

Run php artisan horizon. Do not also run queue:work on payments, default or mail, or two systems will take the same jobs.

On deploy, run php artisan horizon:terminate. Horizon stops its workers after the current job, and the operating system’s process manager starts Horizon again so the workers load the new code. A worker that keeps the old code will keep the old bug.

Restrict the /horizon path to people who should see payloads. The dashboard shows job data, and the job class lesson already kept card numbers out of that data.

02 Close the dashboard to everyone else

Horizon registers a viewHorizon gate. An empty gate, in local, lets every visitor in. In production the gate should name who may open /horizon. A signed-in admin is enough. A guest receives 403.

Example.phpPHP
class Example
{
    protected function gate(): void
    {
        Gate::define('viewHorizon', function (User $user): bool {
            return $user->is_admin === true;
        });
    }
}

The gate does not hide the Redis queue. It hides the UI that prints payloads, wait times and failed exceptions. Keep the supervisor on redis, and keep queue:work off the same queues, or the dashboard’s process list will not match the workers that are actually running. After a deploy, horizon:terminate is what makes those workers pick up this gate and the rest of the new code.

minProcesses of 1 means a quiet queue still has a worker ready. maxProcesses of 10 is the cap when payments and mail are both deep. Raise the cap only when the Redis server and the database can take the extra connections. A worker holds a database connection for the whole job, and ten long payment jobs are ten connections.

Recap

You can run Horizon as the only supervisor, terminate it on deploy, and close the dashboard.

  • Do not also run queue:work on the same queues.
  • horizon:terminate finishes the current job, then the process manager starts the new code.
  • viewHorizon is a gate. An empty gate in local is not the production rule.

Check yourself · 3 questions

Question 1 of 3You deploy new code and do not terminate Horizon. What do the workers run?

Where the series leaves you

Order 1042 has a path you can point at.

  • The request returns before the provider answers.
  • The job payload is the order id and the attempt.
  • Retries wait, then stop.
  • The idempotent handle() leaves the order paid once.
  • The Horizon supervisor lists the payments queue.

Faking the queue in Test-driven Laravel is how you prove the job was pushed.

← PreviousMake the job safe to run twice

Back to the overview

On this page

  • One supervisor, and terminate it on deploy
  • Close the dashboard to everyone else
  • Recap
  • Check yourself

Queues in production

  • 1 Overview

Hand the work off

  • 2 Keep the request short
  • 3 The job class
  • 4 Dispatch from actions, not controllers

When it goes wrong

  • 5 Retries, backoff and timeouts
  • 6 Failed jobs you can replay
  • 7 Unique jobs and WithoutOverlapping
  • 8 Batches and then()

Run it for real

  • 9 Make the job safe to run twice
  • 10 Horizon in production

Queues in production

Lessons

  1. 01Overview

Hand the work off

  1. 02Keep the request short
  2. 03The job class
  3. 04Dispatch from actions, not controllers

When it goes wrong

  1. 05Retries, backoff and timeouts
  2. 06Failed jobs you can replay
  3. 07Unique jobs and WithoutOverlapping
  4. 08Batches and then()

Run it for real

  1. 09Make the job safe to run twice
  2. 10Horizon in production

Get the Friday email

One snippet and the week’s Laravel news, in your inbox every Friday morning. Unsubscribe whenever you like.

laravelmastery

Practical Laravel learning for working developers, made in the UK.

Learn

  • Snippets
  • Articles
  • Series

Stay current

  • News
  • Friday email
  • RSS feed

Company

  • About
  • Privacy
  • Terms
© 2026 Laravel Mastery. Independent and not affiliated with Laravel Holdings Inc.Designed and built by Half Shell Studios

We remember the videos you watch, where you left off, and which series lessons you mark as read, against this connection’s address. Nothing is stored until you agree.