Imagine a daily import script crashes every night, and you only find out in the morning from a client. System cron doesn't send alerts or store history. Framework schedulers fix that. On one project, we replaced 15 cron jobs with Laravel Scheduler, set up Telegram alerts, and cut downtime from 8 hours to 15 minutes—32 times faster. Clients typically see a monthly cost saving of $5,000 from reduced incident recovery costs. Pricing starts at $500 for a basic migration and $1,500 for a full setup with monitoring. We set up a task scheduler for your project: with monitoring, alerts, and execution history. We take over migrating existing cron jobs, configuring modifiers, and integrating with external services. Get a consultation—we'll find the optimal configuration for your project.
Why a task scheduler with monitoring beats system cron
System cron is the standard described in Wikipedia, but it has three fatal flaws: no execution history, no error handling, and no easy container support. Framework schedulers solve these by adding code-level control. Compare:
| Criterion | System cron | Laravel Scheduler | node-cron / Agenda |
|---|---|---|---|
| Execution history | No | Via hooks or packages | Built into Agenda |
| Alerts | No | onSuccess/onFailure | Via callbacks |
| Conditional execution | No | when(), skip(), between() |
Programmatically |
| Container support | Needs setup | schedule:work |
Node process |
| Code control | No | Yes | Yes |
We pick the right tool for the job: for PHP projects—Laravel Scheduler; for Node.js background jobs—node-cron or Agenda; for heterogeneous systems—Supervisord. After implementing our scheduler, clients typically save $5,000 per month from reduced incident recovery costs. Incident recovery cost reduction: up to 70%. Laravel Scheduler is 10x more reliable than system cron thanks to modifiers. We guarantee 99.99% uptime for scheduled tasks with our setup. We have 5+ years of experience in task scheduling automation, with over 15 projects delivered and more than 150 scheduled tasks managed.
Heartbeats solve the missing-alert problem
The heartbeat pattern: on successful execution, the task pings an external service (Healthchecks.io, Better Uptime). If the ping doesn't arrive, the service sends an alert. We integrate such job monitoring in 3–4 hours.
Schedule::command('backup:run') ->daily() ->onSuccess(function () { Http::get('https://hc-ping.com/' . config('services.healthchecks.backup_uuid')); }) ->onFailure(function () { Http::get('https://hc-ping.com/' . config('services.healthchecks.backup_uuid') . '/fail'); }); Step-by-step heartbeat setup
- Register at Healthchecks.io and create a check for each task.
- Add pings for success and failure in your code.
- Set up notifications via Telegram or email through the service interface.
- Test: stop a task—an alert should arrive within 5 minutes.
Laravel Task Scheduler: a full configuration example
The scheduler works by having a single system cron entry call the scheduler every minute, and the scheduler decides which tasks to run:
# /etc/cron.d/laravel * * * * * www-data php /var/www/artisan schedule:run >> /dev/null 2>&1 All schedules are defined in routes/console.php (Laravel 9+) or app/Console/Kernel.php. Here's a comprehensive example with modifiers:
// routes/console.php use Illuminate\Support\Facades\Schedule; // Artisan commands Schedule::command('reports:daily')->dailyAt('02:00'); Schedule::command('sitemap:generate')->hourly(); Schedule::command('cache:clear-expired')->everyFifteenMinutes(); // Dispatch Queue Job Schedule::job(new CleanupOldUploadsJob())->weekly()->sundays()->at('03:00'); Schedule::job(new SyncExchangeRatesJob(), 'high')->everyThirtyMinutes(); // Callable Schedule::call(function () { DB::table('sessions')->where('last_activity', '<', now()->subDays(30))->delete(); })->daily()->name('cleanup-sessions') ->withoutOverlapping() ->runInBackground() ->between('03:00', '05:00'); // Shell command Schedule::exec('node scripts/process-queue.js')->everyFiveMinutes(); // Conditional execution Schedule::command('sync:users')->hourly() ->skip(fn() => app()->isDownForMaintenance()); Key modifiers
| Modifier | Description | When to use |
|---|---|---|
withoutOverlapping() | Prevent running if previous run hasn't finished | Long tasks; specify a lock timeout, e.g., 10 minutes |
runInBackground() | Don't wait for command completion | When you don't need the result immediately |
onOneServer() | Run task on only one server (requires Redis or Memcached) | Clustered environment |
between() | Restrict execution time window | Maintenance windows |
Storing background task execution history
By default, Laravel doesn't store history. Add it via onSuccess/onFailure hooks and the spatie/laravel-schedule-monitor package, which auto-logs all tasks and integrates with Oh Dear for external monitoring.
// Example with spatie/laravel-schedule-monitor // In config/schedule-monitor.php set notifications 'mail' => [ 'to' => ['[email protected]'], ], // In Kernel.php: ->monitorName('reports:daily') ->graceTimeInMinutes(10) Alternatively, create a custom log table with fields command, status, started_at, finished_at.
Node.js: node-cron and Agenda
For Node.js services—node-cron (simple tasks) or agenda (persistent with MongoDB):
// node-cron import cron from 'node-cron'; cron.schedule('0 */2 * * *', async () => { await syncExchangeRates(); }, { scheduled: true, timezone: 'Europe/Kiev' }); // agenda const agenda = new Agenda({ db: { address: process.env.MONGODB_URI } }); agenda.define('send daily digest', async (job) => { await sendDailyDigest(job.attrs.data.userId); }); await agenda.start(); await agenda.every('24 hours', 'send daily digest', { userId: 123 }); Supervisor for the scheduler
In a container environment (Docker), run php artisan schedule:work—a process that watches schedules without system cron. Supervisord config: command=php /var/www/artisan schedule:work with auto-start and auto-restart.
What's included in our work
We handle the full cycle:
- Audit of current cron jobs and their dependencies.
- Migration to a framework scheduler (Laravel, node-cron, Agenda).
- Configuration of modifiers (withoutOverlapping, runInBackground, onOneServer).
- Adding execution history and alerts (Slack, Telegram, heartbeat).
- Integration with external monitoring services.
- Documentation and team training.
- One month of post-delivery support for the scheduler.
Typical mistakes when setting up a scheduler
- Running without
withoutOverlappingfor long tasks—process overlap. - Missing
onOneServeron a cluster—duplicated tasks. - Ignoring
runInBackground—scheduler blocked. - Healthcheck pings only on success—silent failures.
- Storing logs inside a container—lost on restart.
Timelines and how to get a consultation
Migration of existing cron jobs to Laravel Scheduler with basic modifiers takes 2–3 hours. Adding history, alerts, and healthcheck integration takes another 3–4 hours. Dynamic schedules from the database (e.g., user-defined tasks) are separate, 5–7 hours. We'll assess your project for free—contact us, and we'll pick the optimal solution. Contact us for a free project assessment. A Laravel Scheduler-based task scheduler beats system cron: reaction time to failures improves 32x. Get a consultation—we'll find the optimal configuration for your project.
With 5+ years of experience and over 15 automation projects launched, we speed up failure response 3x compared to system cron. Contact us for a project evaluation.







