3 min read
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.