Laravel includes a powerful Task Scheduling system that allows developers to automate repetitive tasks in a clean, expressive, and centralized way. Instead of creating dozens of messy system-level cron entries, Laravel lets you define scheduled commands inside a single file and run them automatically at defined intervals. This makes your application more maintainable, scalable, and easier to manage across different environments.
Scheduled tasks are essential for modern applications. They handle everything from cleaning up outdated data, generating reports, clearing caches, sending scheduled emails, synchronizing third-party APIs, rotating logs, triggering notifications, queue processing, and many other automation tasks. Laravel’s scheduler takes the pain out of setting up cron jobs by allowing you to write them in clean PHP syntax within your application’s code.
In this comprehensive guide, we will explore how Laravel’s Task Scheduler works, how to define schedules, how to create custom commands, how cron expressions work internally, how to run tasks in production, how to schedule job-based automation, how to manage frequencies, how to prevent overlapping tasks, how to use task output logging, and how to monitor and optimize scheduled tasks for performance and reliability.
Why Task Scheduling Is Important
Task scheduling saves time, reduces human error, and automates processes that would otherwise require manual execution. It ensures:
- Predictable, automated workflows
- Better server performance through periodic cleanup
- Reduced load on external services through timed syncs
- Simplified management of recurring tasks
- Centralized automation logic
- Improved reliability and consistency
Laravel’s scheduler makes it easy to define these tasks at the application level rather than relying on system-level cron entries.
How Laravel’s Task Scheduler Works
Laravel uses a single cron entry in the server that triggers Laravel’s internal scheduler every minute. The scheduler then determines which tasks need to be executed.
Flow:
- You define tasks inside
app/Console/Kernel.php - The system cron runs Laravel’s
schedule:runcommand every minute - Laravel checks which tasks are due
- Laravel runs only those tasks that match the current time criteria
This means you only have ONE cron job on the server.
Setting Up the System Cron Entry
On your server, add the following cron entry:
* * * * * php /path/to/artisan schedule:run >> /dev/null 2>&1
Explanation:
* * * * *means run every minuteschedule:runtriggers Laravel’s scheduler- Output is ignored for clean logs
This is the only cron entry needed.
Defining Scheduled Tasks in Kernel.php
All scheduled tasks are defined inside:
app/Console/Kernel.php
Inside the schedule() method, you add task definitions:
protected function schedule(Schedule $schedule)
{
$schedule->command('emails:send')->daily();
}
This example runs the custom command daily.
Types of Tasks You Can Schedule
Laravel allows scheduling:
- Artisan commands
- Queue jobs
- Closure-based tasks
- Shell commands
- Invokable classes
This flexibility makes the scheduler extremely powerful.
Scheduling Artisan Commands
You can schedule any custom or built-in Artisan command.
Example:
$schedule->command('cache:clear')->hourly();
Example custom command:
$schedule->command('emails:send')->daily();
Scheduling Closure-Based Tasks
You can run closures without creating a command:
$schedule->call(function () {
DB::table('logs')->delete();
})->weekly();
Useful for small, quick tasks.
Scheduling Shell Commands
You can run OS-level shell commands:
$schedule->exec('php -v')->everyThirtyMinutes();
Scheduling Queue Jobs Directly
Jobs can be scheduled without needing commands:
$schedule->job(new SyncDataJob)->hourly();
Laravel will push jobs to the queue at the scheduled time.
Scheduling Invokable Classes
Example:
$schedule->call(new GenerateReport)->monthly();
Your class must implement __invoke().
Frequency Methods in the Scheduler
Laravel provides expressive methods for defining frequencies.
Run Every Minute
$schedule->command('sync:tasks')->everyMinute();
Hourly Tasks
$schedule->command('mail:send')->hourly();
Daily Tasks
$schedule->command('reports:generate')->daily();
You can specify times:
$schedule->dailyAt('13:00');
Weekly Tasks
$schedule->weekly();
Specify day:
$schedule->weeklyOn(1, '08:00'); // Monday at 8 AM
Monthly Tasks
$schedule->monthly();
Or:
$schedule->monthlyOn(1, '00:00'); // First day of month
Yearly Tasks
$schedule->yearly();
Using Cron Expressions
You can specify exact cron expressions:
$schedule->command('cleanup:logs')->cron('0 */6 * * *');
Runs every 6 hours.
Preventing Overlapping Tasks
If a slow task overlaps with the next cycle, use:
$schedule->command('emails:send')->withoutOverlapping();
This prevents multiple instances of the same task from running at once.
Limiting Task Execution Based on Conditions
Run only in production:
$schedule->command('backup:run')->daily()->environments('production');
Conditional execution:
$schedule->command('cleanup')->when(function () {
return now()->dayOfWeek === 0; // Sunday
});
Running Tasks in Maintenance Mode
By default, scheduled tasks do not run in maintenance mode.
Enable:
$schedule->command('update:feeds')->evenInMaintenanceMode();
Output Handling and Logging
Send output to a file:
$schedule->command('emails:send')->daily()->sendOutputTo('/storage/logs/emails.log');
Append instead of overwrite:
->appendOutputTo('/storage/logs/emails.log');
Emailing Task Output
You can email the results of tasks:
$schedule->command('backup:run')
->emailOutputTo('[email protected]');
For failure only:
->emailOutputOnFailure('[email protected]');
Task Timeouts
Prevent a task from running too long:
->timeout(60);
Or:
->onOneServer();
This works well with Redis or Memcached locks.
Ensuring Tasks Run Only on One Server
In clustered environments:
$schedule->command('reports:generate')->onOneServer();
Prevents duplicate execution.
Using before and after Hooks
Run logic before a task:
->before(function () {
Log::info('Task starting');
});
After task:
->after(function () {
Log::info('Task completed');
});
Running Tasks Only on Certain Days
->mondays();
->tuesdays();
->weekdays();
->weekends();
Example:
$schedule->command('cleanup:files')->weekdays()->dailyAt('01:00');
Scheduling Tasks for Specific Time Zones
->timezone('America/New_York');
Combining Multiple Schedule Options
$schedule->command('db:backup')
->dailyAt('02:00')
->onOneServer()
->withoutOverlapping()
->timezone('UTC');
Creating a Custom Artisan Command
You can generate a command:
php artisan make:command SendEmailsCommand
This creates:
app/Console/Commands/SendEmailsCommand.php
Defining Logic in Command Handle Method
Example:
public function handle()
{
Mail::to(...)->send(new WeeklyReport());
}
Schedule it:
$schedule->command('emails:send')->weekly();
Using Queues With Scheduled Tasks
Schedule a job instead of running logic inside a command:
$schedule->job(new SendNotificationsJob)->everyFiveMinutes();
Jobs can handle heavy tasks better with queue workers.
Scheduling Artisan Queue Workers
Some tasks involve dispatching many jobs.
$schedule->command('queue:work')->hourly();
Use caution: queue workers should run continuously, not via scheduler.
Scheduling Database Cleanups
Example weekly cleanup:
$schedule->call(function () {
DB::table('sessions')->where('last_activity', '<', now()->subMonths(2))->delete();
})->weekly();
Scheduling Email Reports
$schedule->command('reports:send')->dailyAt('06:00');
Synchronizing With APIs
$schedule->job(new SyncApiJob)->everyTenMinutes();
Regenerating Sitemaps
$schedule->command('sitemap:generate')->daily();
Rotating Logs
$schedule->command('logs:rotate')->daily();
Managing Backups
$schedule->command('backup:run')->daily();
$schedule->command('backup:clean')->weekly();
Scheduling Cache Cleanup
$schedule->command('cache:clear')->monthly();
Scheduling Notifications
$schedule->job(new NotifyInactiveUsers)->daily();
Scheduling Horizon Snapshot
If using Horizon:
$schedule->command('horizon:snapshot')->everyFiveMinutes();
This allows Horizon dashboards to stay updated.
Testing Scheduled Tasks
You can test schedules:
$this->travelTo(now()->startOfDay());
$this->assertScheduled(function (Schedule $schedule) {
return $schedule->command('emails:send')->isDue(now());
});
Debugging Scheduled Tasks
Check logs:
storage/logs/laravel.log
Check cron logs on the server:
/var/log/syslog
Common Mistakes With Laravel Scheduler
- Forgetting to set up the system cron entry
- Using
queue:workinside the scheduler - Overlapping tasks without locking
- Missing permissions on storage/logs
- Long-running tasks without timeouts
- Scheduling heavy tasks during peak traffic
- Running tasks in maintenance mode unintentionally
Best Practices for Laravel Task Scheduling
- Use
onOneServerin clustered environments - Use
withoutOverlappingfor long tasks - Log outputs for debugging
- Use queues for heavy processing
- Run cleanup tasks at off-peak hours
- Keep scheduler definitions organized
- Keep commands small and testable
- Version commands if your API has versions
- Avoid running queue workers inside the scheduler
Example of a Fully Configured Schedule
protected function schedule(Schedule $schedule)
{
$schedule->command('reports:generate')
->dailyAt('03:00')
->withoutOverlapping()
->onOneServer()
->emailOutputOnFailure('[email protected]')
->timezone('UTC');
$schedule->job(new SyncDataJob)
->hourly();
$schedule->call(function () {
DB::table('logs')->where('created_at', '<', now()->subYear())->delete();
})->weekly();
$schedule->command('backup:run')
->daily();
}
Complete Example Custom Command and Schedule
Command:
class SendDailyReport extends Command
{
protected $signature = 'reports:daily';
protected $description = 'Send daily report';
public function handle()
{
Mail::to('[email protected]')->send(new DailyReportMail());
}
}
Schedule:
$schedule->command('reports:daily')->dailyAt('05:00');
Leave a Reply