Introduction
Laravel's scheduler lets you define cron-like recurring tasks in PHP, not in /etc/crontab. You write one system cron entry that calls schedule:run every minute, and from then on every recurring task lives in routes/console.php with readable fluent methods like ->daily() and ->everyFiveMinutes().
Key Concepts
- Scheduler: The machinery that runs every minute and executes any tasks due in that minute.
schedule:run: The artisan command triggered by the system cron — it consults the scheduler and runs anything due.- Frequency modifier: Methods like
->daily(),->hourly(),->everyFifteenMinutes()that control how often a task runs. - Constraint: Methods like
->weekdays(),->between(),->when()that further restrict when a task runs.
Real World Context
Every app has "daily at 2am" work: clean up old sessions, send digest emails, run backups, prune soft-deleted rows. Managing that via crontab means a dozen system-level entries that must be kept in sync with the code. Laravel's scheduler collapses all of it into one file in the repo.
Deep Dive
Why Use the Scheduler?
Traditional Cron: Laravel Scheduler:
┌─────────────────────────────────┐ ┌─────────────────────────────────┐
│ # Server crontab (complex!) │ │ # Server crontab (simple!) │
│ 0 * * * * /path/to/report.php │ │ * * * * * cd /app && artisan │
│ */5 * * * * /path/to/cleanup.php│ │ schedule:run >> /log │
│ 0 0 * * * /path/to/backup.php │ └─────────────────────────────────┘
│ 30 2 * * 0 /path/to/prune.php │
│ ...many more entries... │ All scheduling defined in PHP!
└─────────────────────────────────┘
Defining Schedules
Define schedules in routes/console.php:
phpuse Illuminate\Support\Facades\Schedule; Schedule::command('emails:send')->daily(); Schedule::command('reports:generate')->weeklyOn(1, '8:00'); Schedule::command('cache:clear')->hourly();
Scheduling Artisan Commands
php// Run an Artisan command Schedule::command('emails:send --force')->daily(); // With arguments Schedule::command('user:cleanup', ['--days' => 30])->weekly(); // Signature style Schedule::command('inspire')->hourly();
Scheduling Queued Jobs
phpuse App\Jobs\ProcessReports; use App\Jobs\CleanupDatabase; Schedule::job(new ProcessReports)->daily(); Schedule::job(new CleanupDatabase, 'maintenance')->weekly();
Scheduling Shell Commands
phpSchedule::exec('node /home/user/scripts/backup.js')->daily(); Schedule::exec('mysqldump database > backup.sql')->dailyAt('02:00');
Scheduling Closures
phpSchedule::call(function () { DB::table('recent_users')->delete(); })->daily(); Schedule::call(fn () => cache()->flush())->weekly();
Schedule Frequency Options
php// Time-based Schedule::command('task')->everyMinute(); Schedule::command('task')->everyTwoMinutes(); Schedule::command('task')->everyFiveMinutes(); Schedule::command('task')->everyTenMinutes(); Schedule::command('task')->everyFifteenMinutes(); Schedule::command('task')->everyThirtyMinutes(); Schedule::command('task')->hourly(); Schedule::command('task')->hourlyAt(17); // At :17 past Schedule::command('task')->everyTwoHours(); Schedule::command('task')->daily(); Schedule::command('task')->dailyAt('13:00'); Schedule::command('task')->twiceDaily(1, 13); // 1am and 1pm Schedule::command('task')->weekly(); Schedule::command('task')->weeklyOn(1, '8:00'); // Monday 8am Schedule::command('task')->monthly(); Schedule::command('task')->monthlyOn(4, '15:00'); // 4th at 3pm Schedule::command('task')->quarterly(); Schedule::command('task')->yearly(); Schedule::command('task')->yearlyOn(6, 1, '17:00'); // June 1st 5pm // Custom cron Schedule::command('task')->cron('0 * * * *');
Timezone
phpSchedule::command('report:generate') ->timezone('America/New_York') ->dailyAt('09:00');
Running the Scheduler
Add one cron entry to your server:
bash* * * * * cd /path-to-your-project && php artisan schedule:run >> /dev/null 2>&1
For local development:
bashphp artisan schedule:work # Runs scheduler every minute
Common Pitfalls
- Forgetting the system cron entry — the PHP scheduler only does anything when
schedule:runis called. Without the* * * * *crontab line, your whole schedule is dead code. - Using wall-clock frequencies without a timezone —
->dailyAt('09:00')runs at 9am in the server's PHP timezone, which may not match your users. Chain->timezone('America/New_York')to pin it.
Best Practices
- Keep all schedule definitions in
routes/console.php— one file to audit, one place to change. - Prefer
->dailyAt('HH:MM')over raw->cron('...')— the fluent form reads better in diff and can't have a typo in the five-field cron string.
Summary
- Define recurring tasks in
routes/console.phpwithSchedule::command(),Schedule::job(), orSchedule::call(). - One
schedule:runcron entry per minute drives the entire schedule. - Frequency methods (
->daily(),->hourly(), etc.) combine with constraints (->weekdays(),->between()). schedule:workruns the scheduler locally for development.