Every application has recurring work: clearing old data, sending daily reports, or reminding users. The old way is to add many cron entries on the server — messy and hard to track. Laravel offers a much cleaner approach through the Task Scheduler: you define all schedules in code, and only need a single cron entry on the server. This tutorial covers it thoroughly for Laravel 10/11.
How the Scheduler Works
Instead of many cron entries, you place one cron that calls Laravel every minute. Laravel then checks which schedules are due at that moment and runs them. All schedule definitions live inside your application, so they go into version control and are easy to read.
1. Defining Schedules
In Laravel 11, schedules are defined in routes/console.php. In Laravel 10 and earlier, use the schedule() method in app/Console/Kernel.php. A simple example running a closure daily:
use Illuminate\Support\Facades\Schedule;
Schedule::call(function () {
DB::table('sessions')->where('last_activity', '<', now()->subDays(7))->delete();
})->daily();
You can also schedule an existing artisan command, which is cleaner for complex logic:
Schedule::command('reports:daily')->dailyAt('07:00');
Schedule::command('backup:run')->weekly()->sundays()->at('02:00');
2. Frequency Options
The scheduler provides many readable frequency methods. Some of the most-used:
->everyMinute(); // every minute
->everyFiveMinutes(); // every 5 minutes
->hourly(); // every hour
->dailyAt('13:00'); // every day at 13:00
->weekdays()->at('09:00');// weekdays at 09:00
->monthlyOn(1, '00:00'); // the 1st of each month
You can even constrain with conditions, for example running only in production:
Schedule::command('newsletter:send')
->dailyAt('08:00')
->when(fn () => app()->environment('production'));
3. A Single Cron Entry on the Server
This is the key part. On the production server, add one cron line that runs the scheduler every minute. Open the crontab:
crontab -e
Add the following line (adjust to your application path):
* * * * * cd /var/www/app && php artisan schedule:run >> /dev/null 2>&1
Done. From now on, just change code to add or modify schedules — the server never needs touching again. To test without waiting, run it manually:
php artisan schedule:run
Or run the scheduler interactively locally without cron:
php artisan schedule:work
4. Common Tasks Worth Scheduling
- Cleanup: deleting old logs, expired notifications, or temporary files.
- Reports: emailing a daily sales summary to admins.
- Reminders: notifying users whose invoices are due.
- Sync: pulling data from a third-party API periodically.
Example scheduling report generation while queuing it so it doesn't burden the scheduler process:
Schedule::job(new GenerateSalesReport)->dailyAt('06:00');
5. Preventing Overlaps
If a task runs longer than its interval, two instances can run at once and cause problems. Prevent it with withoutOverlapping():
Schedule::command('import:large-file')
->everyMinute()
->withoutOverlapping();
Laravel skips the next run if the previous instance hasn't finished. If you run multiple servers sharing one database, use onOneServer() so the task runs on only one server:
Schedule::command('reports:daily')
->dailyAt('07:00')
->onOneServer();
6. Logging Output
For auditing and debugging, direct task output to a log file. This is invaluable when a task fails silently:
Schedule::command('backup:run')
->daily()
->appendOutputTo(storage_path('logs/backup.log'));
You can also email output on failure, or ping a URL as a success signal (useful with cron monitoring services):
Schedule::command('backup:run')
->daily()
->emailOutputOnFailure('admin@example.com')
->pingOnSuccess('https://monitor.example.com/ping');
7. Common Mistakes to Avoid
- Forgetting the cron entry on the server: without
schedule:runevery minute, no schedule runs even if the code is correct. - Wrong path in crontab: use an absolute path to the app folder and the correct PHP binary.
- Heavy tasks blocking the scheduler: wrap heavy work as a queued job with
Schedule::job()to keep the scheduler light.
By putting all schedules in code, using a single cron entry, preventing overlaps, and logging output, you get a scheduling system that is clean, trackable, and easy to maintain — the same pattern used in nearly every production Laravel app.