Scheduled Tasks
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.