Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Horizon Cron Supervisor Laravel Package

ralphjsmit/laravel-horizon-cron-supervisor

Run Laravel's scheduled tasks through Horizon instead of system cron. Your schedule is dispatched as Horizon jobs, so it's supervised, monitored, and retried automatically and visible on the Horizon dashboard.

View on GitHub
Deep Wiki
Context7

Getting Started

This package is for teams already running Horizon on Redis who want to keep queue workers alive with cron instead of Supervisor/systemd. You declare when each Horizon supervisor should be active, and a cron-driven command reconciles the running Horizon processes with that schedule. The practical first use case is splitting queues: keep a small always-on supervisor for critical jobs, and schedule a bulk/reporting supervisor to run only during business hours.

Minimal path:

  1. Confirm Horizon works normally first. Run php artisan horizon manually, confirm the dashboard is reachable and a test job processes. Do not debug Horizon and the cron supervisor at the same time.
  2. Install and pin the package.
    composer require ralphjsmit/laravel-horizon-cron-supervisor
    
    Pin the version in composer.json. This is small, single-maintainer glue on the job-processing path.
  3. Publish any package config if the README instructs it. Otherwise start from the defaults.
  4. Add a schedule to supervisors in config/horizon.php. The exact key is documented by the package; the shape is typically a cron expression on each supervisor entry:
    'supervisors' => [
        'default' => [
            'connection' => 'redis',
            'queue' => ['default', 'notifications'],
            'balance' => 'auto',
            'processes' => 2,
            'cron' => '* * * * *', // always eligible
        ],
    
        'reports' => [
            'connection' => 'redis',
            'queue' => ['reports', 'exports'],
            'balance' => 'simple',
            'processes' => 1,
            'cron' => '0 8-18 * * 1-5', // weekdays, 08:00–18:59
        ],
    ],
    
  5. Add the cron entry under the deploy user. If the package registers a Laravel scheduler entry, use the standard scheduler cron:
    * * * * * cd /var/www/app && php artisan schedule:run >> /dev/null 2>&1
    
    If the README gives a direct artisan command, call that every minute instead. Do not run it as root or as the web user unless that is the same user Horizon runs as.
  6. Clear config and hand over. Run php artisan config:cache, terminate any existing Horizon master, and let the next cron tick start workers.
  7. Verify in three places: php artisan horizon:status, the Horizon dashboard, and actual queue depth. A green dashboard with a stalled queue is the failure mode to watch for.

Where to look first:

  • config/horizon.php — the supervisors and queues you already know.
  • The package config — any global defaults, logging, or lock settings.
  • The package command source — especially how it decides Horizon is “running”.
  • Your cron log — cron failures are silent unless you redirect output somewhere.

Implementation Patterns

Always-on critical supervisor + time-windowed bulk supervisor. This is the safest production pattern. Keep default on a * * * * * schedule so transactional jobs never wait for a window, and schedule reports/exports only during business hours. It limits the blast radius: if the cron reconciliation misbehaves, only the scheduled queues are affected.

'default' => [
    'queue' => ['default', 'mail'],
    'processes' => 2,
    'cron' => '* * * * *',
],

'reports' => [
    'queue' => ['reports'],
    'processes' => 1,
    'cron' => '0 7-19 * * 1-5',
],

Business-hours-only environments. Staging, QA, and demo environments are strong fits. Run Horizon only during working hours to cut idle worker cost, and accept that a job submitted at 02:00 waits until 07:00. This is usually fine when there is no SLA and no customer-facing latency requirement.

Nightly batch windows. Put heavy exports and report generation on a dedicated queue and supervisor, and schedule it for a narrow window such as 0 1-3 * * *. Keep it off the default queue so batch load cannot starve interactive jobs.

Deploy pipeline integration. The common failure is calling php artisan horizon:terminate during deploy and waiting for the next cron tick. Add an explicit reconcile step after terminate so workers come back immediately:

php artisan horizon:terminate
# If the package exposes a direct command:
php artisan horizon:cron-supervisor
# If it uses Laravel's scheduler:
php artisan schedule:run

Sequence this after config:cache and after new code is in place. Without it, every deploy can stall the queue for up to a minute.

Independent health checks. Do not let the supervisor be the only thing watching the supervisor. Alert separately on queue depth, job age, and whether a Horizon master exists during an expected window. A simple scheduled check can assert that horizon:status succeeds when the schedule says workers should be active.

Local development stays normal. Run php artisan horizon directly in local development. Do not add cron entries locally; you want immediate feedback, not a one-minute reconciliation loop.

Rollback is intentionally boring. Remove the cron line, re-enable the Supervisor unit or systemd service, run php artisan horizon:terminate, and let the normal process manager restart Horizon. There is no schema change or queue migration to reverse.

Gotchas and Tips

Cron granularity is your real restart SLA. Cron is a polling supervisor. On a good host the tick is one minute; on some shared hosts it is five or fifteen minutes. Measure it by killing the Horizon master on staging and timing recovery. Do not read the documented interval and assume it is the effective one.

Confirm the reconciliation model before relying on it. The package description and community write-ups describe slightly different internals: a liveness poll (“start Horizon if it is not running”) versus a bounded run (“start Horizon, drain for a window, exit, repeat”). They have different failure modes. Read the command source once and confirm:

  • How “Horizon is running” is determined — PID file, process table, or Horizon’s own status.
  • Whether stale or recycled PIDs can produce a false healthy reading.
  • Whether a lock prevents overlapping cron invocations.
  • What happens to in-flight jobs on shutdown.

That is a one-hour review that prevents a production surprise.

Overlap and duplicate masters. If the package uses a bounded-run model, a max runtime longer than the cron interval can start a second Horizon master while the first is still draining. Confirm the lock behavior and keep max runtime comfortably below the cron interval. Two masters against the same queues can double process counts, double Redis connections, and make the dashboard confusing.

Deploy stall is real. horizon:terminate stops the master; under cron nothing restarts it until the next tick. If you deploy frequently, wire the reconcile command into the pipeline as described above. Test it on staging with a deploy while jobs are flowing.

Config cache catches people. Horizon reads supervisor configuration at boot. After editing config/horizon.php, run php artisan config:cache and terminate/restart Horizon. A stale config cache is a common reason a schedule change appears to do nothing.

Timezone discipline. Cron expressions are evaluated against either the server timezone or the application timezone depending on implementation. Set it explicitly, document it in the runbook, and avoid schedules that silently shift when a server is rebuilt in a different region.

Run cron as the right user. The cron user needs write access to storage/, read access to .env, and network access to Redis. Running as root causes log and cache ownership problems; running as the web user can conflict with deploy permissions.

Log the cron output. Silent cron failures are the worst kind. Redirect stdout and stderr to a log file and alert on repeated failures. Also capture horizon:status exit codes in your health check.

Test graceful shutdown with a long job. Dispatch a job that sleeps for 30–60 seconds, trigger a terminate, and confirm the job is released back to the queue rather than lost. Horizon normally handles this well, but the cron supervisor’s shutdown path is the part you are trusting.

Dashboard and metrics will show gaps. If the master exits every cycle, Horizon’s metrics and process list will have gaps. That is expected, but it means “no data” in the dashboard is not automatically an incident. Alert on queue depth and job age instead.

Do not use this where latency is on an SLA. Any workload with a strict job-latency target, high throughput, or multi-node autoscaling should use Supervisor, systemd, Kubernetes, ECS, Vapor, or Forge daemons. Cron is a liveness mechanism, not an event-driven process manager.

Keep a fallback. This is a small package with a single maintainer. Pin the version, keep a tested Supervisor unit or a hand-written scheduled command ready, and make sure at least one other person knows how to switch back. The fallback should be exercised once on staging, not written down and forgotten.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle