Skip to content

Horizon ​

Laravel Horizon: the dashboard and workers of queues on Redis.

kitloom/horizon · admins: kitloom/horizon-filament, kitloom/horizon-nova

Laravel Horizon on a kitloom site. Horizon runs the workers of a queue kept in Redis and shows them in a dashboard of its own: jobs waiting, running, done and failed, retries, throughput and wait times. This module brings Horizon in, lets into its dashboard the users whose role allows it, and shows in the admin whether Horizon is running. The admin section is kitloom/horizon-filament or kitloom/horizon-nova.

Horizon works only when the site's queue is on Redis. A site whose queue stays in the database (QUEUE_CONNECTION=database) does not need this module: Horizon would not see its jobs.

What the server needs ​

WhatWhy
Redis, and the phpredis extension (or the predis/predis package)The queue and Horizon's own records live there
PHP extensions pcntl and posixHorizon starts and stops worker processes. Linux and macOS have them; Windows does not, so Horizon does not run there
A process manager that keeps php artisan horizon runningHorizon is one long-lived process. If it stops, jobs wait in the queue until it is started again
php artisan schedule:run every minute (cron)The module schedules horizon:snapshot, which feeds the Metrics screen; it runs on one server of several, by a lock in the cache, so a site on several servers needs a shared cache store (Redis, the database) — with each server's own cache the lock is local and the task runs on every one

Setting it up ​

  1. Point the queue to Redis in .env:

    dotenv
    QUEUE_CONNECTION=redis
    REDIS_HOST=127.0.0.1
    REDIS_PORT=6379
    REDIS_PASSWORD=null
  2. Start Horizon under a process manager (below). Locally, run php artisan horizon in a terminal instead.

  3. Open the admin: the "Horizon" section has the dashboard, and its badge says running.

php artisan horizon:install is not needed. It would publish a provider into the application to decide who opens the dashboard, and the module decides that already.

supervisor ​

/etc/supervisor/conf.d/horizon.conf. Change the path, the user and the log file to match the server:

ini
[program:horizon]
process_name=%(program_name)s
command=php /var/www/site/artisan horizon
user=www-data
autostart=true
autorestart=true
redirect_stderr=true
stdout_logfile=/var/www/site/storage/logs/horizon.log
stopwaitsecs=3600

Then run supervisorctl reread, supervisorctl update and supervisorctl start horizon. stopwaitsecs gives a long job time to finish before supervisor kills it; it should be longer than the longest job.

systemd ​

/etc/systemd/system/horizon.service:

ini
[Unit]
Description=Laravel Horizon
After=network.target redis.service

[Service]
User=www-data
WorkingDirectory=/var/www/site
ExecStart=/usr/bin/php artisan horizon
Restart=always
TimeoutStopSec=3600

[Install]
WantedBy=multi-user.target

Then run systemctl enable --now horizon.

Forge, Ploi, Docker ​

  • Forge, Ploi: add a daemon with the command php artisan horizon. They run it under supervisor for you.
  • Docker: a container of the site's image with the command php artisan horizon and restart: unless-stopped. The image needs pcntl and posix (docker-php-ext-install pcntl posix).

On every deploy ​

bash
php artisan horizon:terminate

Horizon finishes the jobs it is running and exits; the process manager starts it again with the new code. Without this, the workers keep running the old code.

The badge in the admin ​

BadgeWhat it meansWhat to do
runningHorizon processes the queue—
pausedSomeone ran horizon:pause; jobs waitphp artisan horizon:continue
not runningNothing processes the queue; jobs waitStart php artisan horizon, check the process manager and its log
Redis is downRedis does not answer: neither the queue nor Horizon worksStart Redis, check REDIS_* in .env
queue not on RedisThe site queues jobs somewhere else; Horizon does not see themQUEUE_CONNECTION=redis, or remove the module

The same in the terminal: php artisan horizon:status.

Who opens the dashboard ​

/horizon opens for users whose role has the Open the Horizon dashboard permission (view_horizon). Administrators have it from the start; it is given to other roles on the roles screen. The dashboard shows the jobs' data and can retry and delete them, so give it with care. Everyone else gets "403 Forbidden", in every environment, local included.

An application that decides this itself with Horizon::auth() sets kitloom-horizon.authorize to false.

Deactivating the module ​

Deactivating Horizon on the Modules screen also stops loading laravel/horizon: the dashboard, its routes and the horizon commands go with the module (unless the application requires laravel/horizon in its own composer.json). The process manager's php artisan horizon then fails at its next restart, and the switch restarts it — so first stop that program, or change its command to php artisan queue:work if the site keeps a queue. Horizon's records in Redis stay, and activating the module brings everything back.

Workers, environments, alerts ​

How many workers run, on which queues, and when Horizon adds more is Horizon's own configuration: php artisan vendor:publish --tag=horizon-config writes config/horizon.php, documented in Horizon's docs. Two things to know:

  • Horizon starts workers only for the environments listed under environments in that file: production and local out of the box. A site with APP_ENV=staging publishes the file and adds its environment (or '*', which matches any), or Horizon starts, says it is running, and processes nothing.
  • The dashboard's address is horizon.path (/horizon), and the admin links follow it.

Alerts about long waits (horizon.waits) are sent once the application tells Horizon where: Horizon::routeMailNotificationsTo('[email protected]') in a service provider.

Configuration ​

php artisan vendor:publish --tag=kitloom-horizon-config writes kitloom-horizon.php:

KeyDefaultWhat
authorizetrueThe module decides who opens the dashboard (view_horizon)
snapshottruehorizon:snapshot every five minutes while the queue is on Redis
filament.register, nova.registertrueThe "Horizon" section of each admin

For developers ​

Kitloom\Horizon\HorizonDashboard (bound per request) is what the admins read: status() (running, paused, inactive, unreachable, not-on-redis), label(), tone() (success, warning, danger), explanation(), url(), allows($user), onRedis(). It asks Redis once per request and does not ask at all when the queue is not on Redis, so an admin page never waits for a Redis that is not there.

kitloom — modules and themes for Laravel.
Pricing · Services · License · Refunds · Privacy · Community · [email protected]