paulund
#laravel #scheduling #devops

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:run is 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.