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

Cron Manager Laravel Package

norvutec/cron-manager

norvutec/cron-manager is a Laravel package for managing scheduled tasks in one place: define and run cron jobs, track execution status and history, and keep an eye on failing tasks without digging through server crontabs.

View on GitHub
Deep Wiki
Context7

Getting Started

Version 7.0.1 is a patch release on the 7.0 line — no migration steps beyond what 7.0.0 required. Treat this assessment as covering the 7.0.x series; if you are coming from 6.x, read the 7.0.0 upgrade notes first (major versions of this package have historically bumped PHP/Laravel minimums and renamed tables/namespaces).

Minimal setup:

composer require norvutec/cron-manager
php artisan vendor:publish --provider="NorvuTec\CronManager\CronManagerServiceProvider"
php artisan migrate

Publish the config and migrations specifically (--tag=cron-manager-config, --tag=cron-manager-migrations) if the provider tag publishes more than you want. Then:

  1. config/cron-manager.php — read this file top to bottom before anything else. It is where the table names, route prefix, middleware, timezone, and lock/overlap behaviour live. Nearly every "it doesn't work" issue traces back to a key here.
  2. The migrations — confirm the table names and columns, because you will be seeding and querying them directly.
  3. The runner command — the package does not install a system cron entry. You still need the one real crontab line:
* * * * * cd /var/www/app && php artisan schedule:run >> /dev/null 2>&1

and, in routes/console.php (Laravel 11+) or app/Console/Kernel.php:

Schedule::command('cron-manager:run')->everyMinute()->withoutOverlapping();

Confirm the exact command name in the config/service provider — it is the single point of failure for the whole package.

First use case: pick two or three $schedule->command(...) lines that ops regularly asks you to change (frequency, on/off during incidents). Move them into cron-manager rows, verify the runs table records executions, then delete the hardcoded entries. Do not migrate the whole schedule at once.

Implementation Patterns

Let the package own when, not what. Keep the actual work in artisan commands or queued jobs; cron-manager stores the expression, timezone, enabled flag, and run history. This keeps the schedule changeable without a deploy while keeping logic testable and version-controlled.

Seed your schedule, don't click it. Define jobs in a seeder so local, staging, and prod converge:

CronJob::updateOrCreate(
    ['command' => 'reports:nightly'],
    ['expression' => '0 2 * * *', 'timezone' => 'Europe/London', 'enabled' => true],
);

Clicking in the UI is fine for hotfixes, but a prod-only manual row is invisible drift that bites on the next environment rebuild.

Use toggles for incident response. During an outage, disabling a job through the UI is often safer and faster than reverting a deploy. Make sure whoever is on call knows the URL and that they have authorization to reach it.

Overlap protection at two levels. withoutOverlapping() on the scheduler entry protects the runner; a per-job lock protects a single long-running job when others are short. A */5 * * * * job that occasionally takes 8 minutes will otherwise stack up silently.

Route heavy work through the queue. If a command can exceed the minute, wrap it in a job implementing ShouldQueue and keep the cron entry as a thin dispatcher. The manager records the dispatch; the queue handles retries and timeouts where you already have visibility.

Testing. Freeze time and assert registration rather than wall-clock behaviour:

$this->travelTo(now()->setTime(2, 0));
$this->artisan('cron-manager:run')->assertSuccessful();

Fake the bus/queue for anything dispatched. For the management routes, cover the authorization path (allowed vs. forbidden) — that is the highest-risk surface in the package.

Deploy checklist: php artisan migrate --force, php artisan config:clear (or rebuild config:cache), and php artisan queue:restart if you changed job definitions.

Gotchas and Tips

  • One real cron entry drives everything. If schedule:run is not firing, the UI's "run now" and the run history are the only things that will appear to work. Check php artisan schedule:list and the server crontab first, not the package config.
  • Timezone double-counting. Expressions are evaluated against the app timezone unless the job row overrides it. A job set to 0 2 * * * plus a server in UTC plus an app in America/New_York gives you 2am in the wrong place. Set app.timezone deliberately and store per-job timezones where the package supports it.
  • Overlap locks need a shared cache store. withoutOverlapping() relies on cache locks; with CACHE_STORE=file on a multi-node deployment, each node gets its own lock and the protection is illusory. Use redis or the database store.
  • Config caching surprises. After changing config/cron-manager.php in production, a cached config silently keeps the old values. Always config:clear and re-cache as part of the deploy.
  • The management UI is a privileged surface. Verify the route middleware in config — at minimum web + auth, ideally a gate/policy. Do not assume the package ships it locked down; check before exposing the prefix publicly, and consider restricting it to an internal network or IP allowlist.
  • 7.0.1 has no changelog detail. The linked diff is the only record, which means no migration guidance. If you hit an unexpected behaviour change from 7.0.0, read the commit diff rather than assuming it is your code — and pin the exact patch version ("norvutec/cron-manager": "7.0.1") until you have verified the runner in staging.
  • File permissions for lock/cache. If the package writes lock or log files, the web user and the cron user must be the same, or at least share group write on storage/. Mismatched users produce locks that are never released.
  • Extension points worth knowing: publishable views (--tag=views) for branding the UI, custom job types if the package supports registering command resolvers, and the run-history model for wiring your own alerting (a failed run is only useful if something watches it — hook the runs table into your existing notification channel rather than relying on the UI).
  • Debugging order: runs/history table → storage/logs/laravel.log for the runner command → the raw query against the jobs table to confirm the row is enabled and the expression parses → the server crontab. Nine times out of ten the problem is step four.
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.
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin