Scheduled Tasks in Laravel
How to define and manage scheduled tasks in Laravel using the task scheduler, including cron setup, common schedule frequencies, and best practices.
Laravel's task scheduler lets you define all of your scheduled jobs in PHP, avoiding the need to manage multiple cron entries. A single cron entry on the server runs the scheduler every minute, and Laravel decides which tasks are due.
Setting Up the Cron Entry
Add a single entry to your server's crontab:
* * * * * cd /path-to-your-project && php artisan schedule:run >> /dev/null 2>&1
That's the only server-level configuration needed. All scheduling logic lives in your application code.
Defining Schedules
Define your schedule in routes/console.php or in a service provider using the Schedule facade:
use Illuminate\Support\Facades\Schedule;
Schedule::command('reports:daily')->dailyAt('06:00');
Schedule::command('cache:prune')->hourly();
Schedule::job(new ProcessPendingPayments)->everyFiveMinutes();
Frequency Options
Laravel provides a wide range of frequency helpers:
->everyMinute()
->everyFiveMinutes()
->everyTenMinutes()
->everyThirtyMinutes()
->hourly()
->hourlyAt(17) // At 17 minutes past each hour
->daily()
->dailyAt('13:00')
->twiceDaily(1, 13) // At 01:00 and 13:00
->weekly()
->weeklyOn(1, '8:00') // Monday at 8am
->monthly()
->monthlyOn(4, '15:00') // 4th of each month at 3pm
->quarterly()
->yearly()
->cron('0 */3 * * *') // Custom cron expression
Preventing Overlaps
By default, a scheduled task will run even if the previous instance is still running. Use withoutOverlapping() to prevent this:
Schedule::command('reports:generate')
->daily()
->withoutOverlapping();
This acquires a cache lock before running. If the lock is already held, the task is skipped.
Running Tasks on One Server
In a multi-server setup, every server will execute the scheduled tasks. Use onOneServer() to ensure a task runs only on a single server:
Schedule::command('db:backup')
->daily()
->onOneServer();
This requires a shared cache driver (Redis or Memcached). The first server to acquire the lock runs the task; the others skip it.
Running Tasks in the Background
By default, scheduled tasks run in the foreground, blocking subsequent tasks. Use runInBackground() to allow concurrent execution:
Schedule::command('reports:weekly')
->weekly()
->runInBackground();
Handling Output
Redirect command output to a log file for debugging:
Schedule::command('reports:daily')
->daily()
->sendOutputTo(storage_path('logs/reports.log'));
// Append instead of overwrite
->appendOutputTo(storage_path('logs/reports.log'));
// Email output when the command produces any output
->emailOutputTo('[email protected]');
Running the Scheduler Locally
During development, run the scheduler in the foreground to watch it execute tasks as they become due:
php artisan schedule:work
Testing Scheduled Tasks
Use Schedule::fake() to assert that tasks were scheduled without actually running them:
use Illuminate\Support\Facades\Schedule;
Schedule::fake();
// Boot your application...
Schedule::assertRanOn('app:send-reminders', now()->subMinutes(5));
To test the actual command logic, call it directly in a feature test:
php artisan test --filter=DailyReportCommandTest
Tips
- Keep individual scheduled commands short and focused. Long-running tasks belong in a queue job dispatched by the scheduled command.
- Log the start and end of each scheduled task so you can detect silent failures.
- Use
withoutOverlapping()for any task that modifies shared state. - In production, monitor that
schedule:runis actually being invoked. A misconfigured cron entry is a common silent failure. Laravel Pulse or an uptime monitor can alert you if key scheduled tasks stop running.
Newsletter
A weekly newsletter on React, Next.js, AI-assisted development, and engineering. No spam, unsubscribe any time.