lkaemmerling/laravel-horizon-prometheus-exporter
Expose Laravel Horizon queue metrics to Prometheus for monitoring and alerting. Track jobs, queues, wait times, throughput, and worker health via a scrapeable endpoint, making it easy to visualize Horizon performance in Grafana and catch backlog or failure issues early.
composer require lkaemmerling/laravel-horizon-prometheus-exporter. The service provider is auto-discovered; on Laravel 13.x you need v1.8.3 or later (that is the only change in this release — a compatibility bump, no API changes).php artisan horizon:status reports running before debugging the exporter.php artisan vendor:publish and pick the package's config tag — check the README for the exact tag in your version) and read it top to bottom. It is small and controls the route path, middleware, and which collectors run.curl -s https://your-app.test/horizon/metrics | head -50. You should see Prometheus text exposition format (# HELP, # TYPE, metric_name{labels} value). If you get HTML or a 302, it is a routing/auth problem, not a metrics problem.failed increasing. That is 90% of the value of this package and takes an afternoon.metrics_path, a scrape_interval of 30–60s, and credentials if you protect the route. Keep it in the same job group as your other Laravel exporters so timestamps line up in dashboards.auth middleware, a bearer-token middleware, or an IP allowlist at the ingress. Do this before the first production deploy, not after.config/horizon-prometheus-exporter.php per environment. Local metrics are usually noise because Horizon often is not running on dev machines.sum() aggregations.rate(<failed_jobs_total>[5m]) > 0 per queue for immediate failure alerts.max_over_time(<waiting_jobs>[10m]) > N for backlog alerts, scoped by queue label.queue, connection) so a single Grafana variable can drive every panel.config/horizon.php's use connection against the exporter's config.prometheus_client_php registry throws if you register the same metric name twice. If you add custom collectors, reuse the package's registry rather than constructing a second one, and guard against double registration inOctane/long-running workers.php artisan config:clear (and re-cache in deploy). A stale cached route path is a classic 404-after-deploy cause.php artisan route:list | grep -i metric → check middleware → curl -v with and without credentials → storage/logs/laravel.log → php artisan horizon:status. That sequence resolves nearly every report.^1.8.3 if you plan to move to 13.How can I help you explore Laravel packages today?