Skyline

A more reliable Laravel queue manager, built for production.

Skyline is a drop-in replacement for Horizon with improved queue visibility, controls and alerting, for the things that break in production, like a stuck unique lock or a worker with a memory leak. We run it on our own production queues, 120,000 jobs a day.

replace

Composer swaps out laravel/horizon, and the Laravel\Horizon namespace stays

/horizon

Same dashboard URL, same config/horizon.php and viewHorizon gate

No migration

The extra tracking data sits under your existing Redis prefix

Your Redis

Runs inside your app, with no agent and no outside service

Upstream releases land within 14 days. Each Skyline release is a recent Horizon tag with our commits replayed on top, so Horizon's fixes and Laravel compatibility updates reach you too.

New in 1.5: alerts

Know when a queue stops working before your users do.

Horizon sends one notification, for a long queue wait, and never says when the wait ended. Skyline watches for the failures that happen quietly, from a stalled queue or a crash-looping worker to Horizon not running at all. It tells you once, routed by severity, and again when the problem clears.

It waits before it pages
A condition has to hold for its window, so a burst the queue absorbs in two minutes wakes nobody.
It stays quiet after a deploy
Notifications wait five minutes while the checks keep counting, so a real breakage still reports.
setup
HORIZON_ALERTS=true
HORIZON_ALERT_SLACK="https://hooks.slack.com/..."

// routes/console.php
Schedule::command('horizon:check')->everyMinute();
#queue-alerts

[critical] Acme: The "emails" queue has stopped moving

03:12

No job on the "emails" queue has finished for at least 6 minutes, although 1,284 jobs are waiting and 4 workers are assigned to it. A worker may be stuck on a job, or have lost its connection without noticing.

Queue
redis:emails
Waiting
1,284
Workers
4
Oldest job
7 minutes

[critical] Resolved: Acme: The "emails" queue has stopped moving

03:20

This had been the case for 14 minutes.

What it watches out of the box

  • ▸ Horizon not running at all
  • ▸ A queue waiting past its threshold
  • ▸ A queue that has stopped moving
  • ▸ A backlog that is not draining
  • ▸ A queue left paused
  • ▸ Jobs failing in bulk, per queue or job class
  • ▸ Workers crash-looping
  • ▸ Workers recycled for their memory limit
  • ▸ A job class that keeps timing out
  • ▸ Jobs dropped by rate-limiting or overlap middleware
  • ▸ A stranded unique or overlap lock
  • ▸ A timeout that will run jobs twice
  • ▸ A supervisor out of memory
  • ▸ The master out of memory
  • ▸ A worker that will not launch
  • ▸ A job that will run twice after its reservation expired

Over mail, Slack, SMS or a JSON webhook for PagerDuty and Opsgenie, with each severity routed to its own channels.

What Skyline adds on top of Horizon.

Unique jobs

Every held ShouldBeUnique lock, with a Release button that checks the owner

When a unique lock gets stuck, dispatch() keeps returning, nothing is queued and Laravel logs nothing. Skyline lists every held lock with how long it has been held and how many dispatches it has skipped, flags the ones whose job is already gone, and releases locks itself when you delete jobs or an until-processing job fails on pickup.

The Locks & Limits screen listing held ShouldBeUnique locks, each with how long it has been held, when it expires, the job holding it and how many dispatches it has skipped, beside a Release button.
Every held lock, the dispatches it skipped, and a Release button.

Metrics & insights

Memory and CPU per job class, and why each worker restarted

The metrics screen plots one line across all queues instead of a sparkline per job class, with 24-hour workload, wait and failure trends. Turn on insights and it also shows the memory and CPU each job class costs, what the whole worker fleet is using, and every unplanned worker restart with its reason.

The Breakdown by Job table on the metrics screen, listing each job class with its peak memory, CPU time and heap growth.
Peak memory, CPU and heap growth for each job class.

Logging

A log line for every job transition, with the job id on it

Set log_channel and Skyline logs each job as it is queued, reserved, released, retried, timed out, completed or failed, so one grep gives you a job's whole history. Rate limiter releases, expired reservations and dispatches dropped by a unique lock are logged with their reason.

storage/logs/queue.log
[job:8813] queued onto [default] (delayed 30s)
[job:8813] reserved from [default] and started processing.
[job:8813] released back to [default] (reason=rate_limited, delay=15s)
[job:8813] failed on [default] and will not be retried (RuntimeException).

Prometheus

A Prometheus endpoint and a Grafana dashboard in the package

Every number on the dashboard, labelled by queue and job class, is scrapeable once you switch the endpoint on, so it sits next to the rest of your metrics. vendor:publish --tag=horizon-grafana writes a 27-panel Grafana dashboard that reads from it.

.env
HORIZON_PROMETHEUS_ENABLED=true

# then scrape
GET /horizon/prometheus

Skyline next to Horizon.

Capability Horizon Skyline
Drop-in for laravel/horizon (same config & namespaces)
Alerts when a queue stalls, jobs fail in bulk or Horizon stops, with recovery messages Long wait only, never resolves
See held unique locks and release a stuck one Find the key in Redis
Memory and CPU per job class and across workers Not measured
Worker restarts recorded with their reason Not recorded
Every job transition written to a log channel Job exceptions only
Prometheus endpoint and Grafana dashboard Dashboard only
Pause and resume a single queue Whole supervisor, from the CLI
Proportional queue priority without starvation Strict priority only

Switching is a Composer change.

Skyline replaces laravel/horizon in Composer. The dashboard stays at /horizon, your config/horizon.php and gate keep working, and there is no migration. Going back is one composer remove.

Installation guide →
terminal
# 1. your Anystack license key
composer config --global \
  --auth http-basic.laravel-skyline.composer.sh \
  you@example.com YOUR-LICENSE-KEY

# 2. replaces laravel/horizon
composer require boring-o11y/laravel-skyline

# 3. same as before
php artisan horizon:install && php artisan horizon

$139, paid once

One license covers every application your team runs it on. Renewing is optional.

$139 one time
one license

No per-app fee, no seats.

  • ▸ Twelve months of upgrades, upstream Horizon merged within 14 days
  • ▸ Every version from that year, yours to reinstall forever
  • ▸ No runtime license check, so an expired year cannot stop your queues
  • ▸ 30 days to change your mind, refunded in full

Secure checkout by Anystack, with cards and VAT invoices.

Frequently asked questions

What is Skyline for Laravel?

+

Skyline is a commercial fork of Laravel Horizon, maintained by Boring Observability. It keeps everything Horizon does for monitoring Redis queues and adds what Horizon cannot see or do: alerts when a queue stalls, jobs fail in bulk, workers crash-loop or Horizon stops running, a screen of held unique locks with a Release button, memory and CPU cost per job class, worker restarts with their reason, a log line for every job transition, a Prometheus endpoint with a Grafana dashboard, and dashboard controls such as pausing a single queue, running a delayed job now and searching the jobs inside a queue.

Is Skyline for Laravel a drop-in replacement for Laravel Horizon?

+

Yes. Skyline keeps the Laravel\Horizon\ namespace and "replaces" laravel/horizon in Composer, so your existing config/horizon.php, service-provider customizations, gate definitions and Artisan commands all keep working. The dashboard still mounts at /horizon. There are no code changes and no migrations.

Will Skyline keep up with Laravel Horizon?

+

Yes. Skyline's main branch is always a recent upstream Horizon tag with the Skyline commits replayed on top, and every upstream release is merged within 14 days. New Horizon versions, bug fixes and Laravel compatibility updates reach you on that schedule. We also run Skyline on our own production queues, so we need it current too.

Can Skyline alert me when my Laravel queues stop working?

+

Yes, from version 1.5. Set HORIZON_ALERTS=true with a Slack, mail, SMS or webhook destination and schedule php artisan horizon:check every minute. Skyline then checks for Horizon not running, queues that are waiting too long, stalled, not draining or left paused, jobs failing in bulk, crash-looping workers, stranded unique and overlap locks, a timeout that will run jobs twice, and processes out of memory. Each alert has to hold before it is sent, repeats at most every 15 minutes, is routed by severity, stays quiet for five minutes after a deploy and sends a recovery message when it clears. Horizon's own long wait notification stands down while alerts are on.

Why does a ShouldBeUnique job stop dispatching, and how does Skyline help?

+

Its unique lock is still held. While it is, every dispatch returns without queueing anything, and Laravel records no failed job and writes no log line. Locks get left behind when a job is deleted from Redis or its queue is cleared, when a ShouldBeUniqueUntilProcessing job fails before it starts, and when a worker is killed holding a lock that has no uniqueFor. Skyline lists the locks your dispatches hold on its Locks & Limits screen, flags the ones whose job is already gone and lets you release one from the dashboard. It also releases the lock itself when you delete jobs and when an until-processing job is refused on pickup.

Is Skyline safe to run in production, and what data leaves my servers?

+

None of it. Skyline is a package that runs inside your application against your own Redis, exactly like Horizon: no agent, no external service, no telemetry. Your job payloads and queue metrics never leave your infrastructure. The license key is used only by Composer when installing or updating the package, so there is no runtime license check that could phone home or interrupt job processing. Dashboard access is governed by your existing viewHorizon gate.

How is Skyline licensed and priced, and what happens after twelve months?

+

Skyline is a one-time purchase of $139. One license covers every application your team runs it on, with no per-app fee and no seat count. The purchase includes twelve months of upgrades: every Skyline release and every upstream Horizon merge published in that year. When the twelve months end nothing stops working. Every version you received stays yours, keeps running, and can still be pulled from the private registry when you rebuild. There is no runtime license check, so an expired upgrade window is a build-time consideration at worst and never a production one. The only thing you lose is access to releases published after your year, until you renew. Renewing is optional and buys the next twelve months at the rate you bought at. The source is MIT; what you are buying is registry access and the maintenance behind it.

What if Skyline is not a fit? Do you offer a refund?

+

Yes. Every purchase comes with a 30-day money-back guarantee. Email tech@boring-observability.dev within 30 days of being charged and we refund it in full, without a call or a form. Rolling back to Horizon is a single Composer command with no migration to unwind.

What support do I get when I buy Skyline?

+

Email support at tech@boring-observability.dev, straight to the people who build and run Skyline. There is no support tier and no ticket queue in between. We aim to reply within one business day, and security reports are triaged the same day.

How do I install Skyline, and can I switch back to Horizon?

+

Authenticate Composer with your license key, add the laravel-skyline.composer.sh registry to composer.json, then run composer require boring-o11y/laravel-skyline, php artisan horizon:install and php artisan horizon. Switching back is composer remove boring-o11y/laravel-skyline followed by composer require laravel/horizon. Skyline stores its extra tracking data under the same Redis prefix and adds no schema, so there is no migration to unwind and nothing to export.

Try it on your own queues.

Checkout ends with a license key. Add it to Composer, require the package and deploy, and the dashboard stays at /horizon with the new screens alongside.

Buy Skyline for $139

Secure checkout by Anystack. 30 days to change your mind.

Questions first? Email us and one of the people who builds it will answer.