Mastering Laravel Task Scheduling
In the past, scheduling tasks on a server required adding multiple entries to the server's crontab configuration. Laravel's Command Scheduler allows you to define your command schedule fluently and expressively within Laravel itself.
Why Use Laravel Scheduler?
Traditional cron has limitations:
- Version control: Crontab lives on the server, not in your repository
- Environment differences: Dev, staging, and production have different crontabs
- Readability:
0 3 * * 1is cryptic - Testing: You can't unit test crontab entries
Laravel's scheduler solves all of these by moving scheduling logic into your application code.
Enabling the Scheduler
You only need to add one single Cron entry to your server to run the scheduler every minute:
* * * * * cd /path-to-your-project && php artisan schedule:run >> /dev/null 2>&1
That's it. Laravel takes over from there.
Defining Schedules
You define your scheduled tasks in the routes/console.php file (or app/Console/Kernel.php in older Laravel versions).
Scheduling Artisan Commands
use Illuminate\Support\Facades\Schedule;
Schedule::command('emails:send')->daily();
// With arguments
Schedule::command('report:generate --type=sales')->weeklyOn(1, '8:00');
Scheduling Closures
Schedule::call(function () {
DB::table('recent_users')->delete();
})->daily();
// Named closure for better logging
Schedule::call(function () {
Cache::flush();
})->daily()->name('cache-flush');
Scheduling Shell Commands
Schedule::exec('node /home/forge/script.js')->daily();
// With output
Schedule::exec('backup.sh')->daily()->sendOutputTo('/var/log/backup.log');
Scheduling Queued Jobs
Schedule::job(new ProcessPodcast)->everyFiveMinutes();
// On specific queue
Schedule::job(new ProcessPodcast, 'podcasts', 'sqs')->everyFiveMinutes();
Schedule Frequencies
Laravel offers a huge variety of frequencies:
->everyMinute() // Every minute
->everyTwoMinutes() // Every 2 minutes
->everyFiveMinutes() // Every 5 minutes
->everyTenMinutes() // Every 10 minutes
->everyFifteenMinutes() // Every 15 minutes
->everyThirtyMinutes() // Every 30 minutes
->hourly() // Every hour
->hourlyAt(17) // At minute 17 of every hour
->everyOddHour() // Every odd hour
->everyTwoHours() // Every 2 hours
->daily() // Every day at midnight
->dailyAt('13:00') // Every day at 1 PM
->twiceDaily(1, 13) // At 1 AM and 1 PM
->weekly() // Every Sunday at midnight
->weeklyOn(1, '8:00') // Every Monday at 8 AM
->monthly() // First day of month at midnight
->monthlyOn(4, '15:00') // 4th day of month at 3 PM
->quarterly() // First day of quarter
->yearly() // January 1st at midnight
->weekdays() // Weekdays only
->weekends() // Weekends only
->sundays() // Sundays only
->mondays() // Mondays only
Custom Cron Expressions
Schedule::command('emails:send')->cron('0 */2 * * 1-5');
Preventing Task Overlaps
By default, scheduled tasks will run even if the previous instance is still running. To prevent this:
Schedule::command('emails:send')->withoutOverlapping();
// With custom lock timeout (in minutes)
Schedule::command('emails:send')->withoutOverlapping(10);
Running Tasks on One Server
If your application runs on multiple servers, you don't want the same job running on all of them simultaneously. Use onOneServer():
Schedule::command('report:generate')
->fridays()
->onOneServer();
Note: This requires a centralized cache driver like Redis or Memcached.
Background Execution
To ensure lengthy tasks don't block the scheduler from starting other tasks, execute them in the background:
Schedule::command('analytics:report')->daily()->runInBackground();
Conditional Scheduling
Run tasks based on conditions:
// Environment-based
Schedule::command('emails:send')
->daily()
->environments(['production']);
// Callback-based
Schedule::command('emails:send')
->daily()
->when(function () {
return config('app.send_emails');
});
// Skip condition
Schedule::command('emails:send')
->daily()
->skip(function () {
return app()->isDownForMaintenance();
});
Hooks: Before and After
Execute code before or after a task:
Schedule::command('emails:send')
->daily()
->before(function () {
Log::info('Starting email send...');
})
->after(function () {
Log::info('Email send completed.');
});
Ping URLs
Notify external services when tasks run:
Schedule::command('emails:send')
->daily()
->pingBefore('https://healthcheck.io/start/xxx')
->pingOnSuccess('https://healthcheck.io/complete/xxx')
->pingOnFailure('https://healthcheck.io/fail/xxx');
Task Output
Capture and send task output:
// Save to file
Schedule::command('emails:send')
->daily()
->sendOutputTo('/var/log/emails.log');
// Append to file
Schedule::command('emails:send')
->daily()
->appendOutputTo('/var/log/emails.log');
// Email output
Schedule::command('report:generate')
->weekly()
->emailOutputTo('admin@example.com');
// Email only on failure
Schedule::command('report:generate')
->weekly()
->emailOutputOnFailure('admin@example.com');
Testing Your Schedule
List all scheduled tasks:
php artisan schedule:list
Run the scheduler once (for testing):
php artisan schedule:run
Run a specific scheduled task immediately:
php artisan schedule:test
Monitoring with Laravel Pulse
If you use Laravel Pulse, schedule metrics are automatically captured:
Schedule::command('emails:send')
->daily()
->name('daily-emails'); // Named tasks show in Pulse
Laravel's scheduler puts the power of automation back into your codebase, making it version-controlled, testable, and easy to manage. No more SSH-ing into servers to edit crontab.