Laravel

Scheduling

Laravel's task scheduler allows you to define scheduled commands fluently within your application, eliminating the need for multiple cron entries on the server.

Defining Schedules: All scheduled tasks are defined in routes/console.php (Laravel 11+) or in the schedule() method of App\Console\Kernel (Laravel 10 and below).
📄routes/console.php
PHP
<?php

use Illuminate\Support\Facades\Schedule;

// Run a closure daily at midnight
Schedule::call(function () {
    DB::table('old_sessions')->where('last_activity', '<', now()->subDays(7))->delete();
})->daily();

// Run an Artisan command every minute
Schedule::command('emails:send')->everyMinute();

// Run a shell command weekly on Sunday at 1am
Schedule::exec('node /home/forge/script.js')->weekly()->sundays()->at('1:00');

// Run a job every five minutes
Schedule::job(new App\Jobs\GenerateReport)->everyFiveMinutes();

// Run with a custom cron expression
Schedule::command('app:sync-data')->cron('0 9 * * 1-5'); // Weekdays at 9am
Schedule Frequency Methods: Laravel provides many convenient frequency helpers to define how often a task should run.
📄routes/console.php
PHP
<?php

use Illuminate\Support\Facades\Schedule;

Schedule::command('app:task')
    ->everySecond()          // Every second
    ->everyTwoSeconds()      // Every 2 seconds
    ->everyFiveSeconds()     // Every 5 seconds
    ->everyMinute()          // Every minute
    ->everyTwoMinutes()      // Every 2 minutes
    ->everyFiveMinutes()     // Every 5 minutes
    ->everyTenMinutes()      // Every 10 minutes
    ->everyThirtyMinutes()   // Every 30 minutes
    ->hourly()               // Every hour
    ->hourlyAt(17)           // Every hour at 17 minutes past
    ->daily()                // Every day at midnight
    ->dailyAt('13:00')      // Every day at 13:00
    ->twiceDaily(1, 13)      // Every day at 1:00 and 13:00
    ->weekly()               // Once a week (Sunday at 00:00)
    ->weeklyOn(1, '8:00')   // Every Monday at 8:00
    ->monthly()              // Once a month (1st at 00:00)
    ->monthlyOn(4, '15:00') // Every month on 4th at 15:00
    ->quarterly()            // Quarterly
    ->yearly()               // Once a year
    ->cron('* * * * *');    // Custom cron expression
Schedule Constraints: Limit when a scheduled task runs using constraint methods like weekdays(), between(), when(), and environments().
📄routes/console.php
PHP
<?php

use Illuminate\Support\Facades\Schedule;

// Only run on weekdays
Schedule::command('app:report')->daily()->weekdays();

// Only run between 8am and 5pm
Schedule::command('app:check')->everyMinute()->between('8:00', '17:00');

// Skip if condition is true
Schedule::command('app:process')->daily()->skip(function () {
    return Carbon\Carbon::today()->isHoliday();
});

// Only run if condition is true
Schedule::command('app:sync')->hourly()->when(function () {
    return Feature::active('sync-enabled');
});

// Only run in specific environments
Schedule::command('app:cleanup')->daily()->environments(['production', 'staging']);

// Prevent overlapping (task will not run if already running)
Schedule::command('app:import')->everyMinute()->withoutOverlapping();

// Run even in maintenance mode
Schedule::command('app:heartbeat')->everyMinute()->evenInMaintenanceMode();
Defining Artisan Commands: You can define custom Artisan commands that can be scheduled using php artisan make:command.
📄app/Console/Commands/SendDailyDigest.php
PHP
<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use App\Models\User;
use App\Mail\DailyDigest;
use Illuminate\Support\Facades\Mail;

class SendDailyDigest extends Command
{
    protected $signature = 'emails:daily-digest {--limit=100 : Max users to email}';
    protected $description = 'Send the daily digest email to all subscribers';

    public function handle(): int
    {
        $limit = $this->option('limit');

        $users = User::where('subscribed', true)->limit($limit)->get();

        $this->withProgressBar($users, function (User $user) {
            Mail::to($user)->send(new DailyDigest($user));
        });

        $this->newLine();
        $this->info("Daily digest sent to {$users->count()} users.");

        return Command::SUCCESS;
    }
}
Setting Up Cron on Server: You only need one cron entry on your server. This calls schedule:run every minute, and Laravel decides which tasks are due.
📄Server Crontab (crontab -e)
BASH
# Edit crontab
crontab -e

# Add this single entry – runs Laravel scheduler every minute
* * * * * cd /var/www/html/myapp && php artisan schedule:run >> /dev/null 2>&1

# For a specific PHP version
* * * * * cd /var/www/html/myapp && /usr/bin/php8.2 artisan schedule:run >> /dev/null 2>&1

# To log output instead of discarding it
* * * * * cd /var/www/html/myapp && php artisan schedule:run >> /var/log/laravel-schedule.log 2>&1
Useful Scheduler Artisan Commands: Commands to run, test, list, and interrupt scheduled tasks from the CLI.
📄Terminal
BASH
# Manually trigger the scheduler (run all due tasks)
php artisan schedule:run

# List all scheduled tasks and their next run time
php artisan schedule:list

# Run the scheduler continuously (every minute) without a cron job — great for local dev
php artisan schedule:work

# Test a specific command without waiting for it to be scheduled
php artisan schedule:test

# Interrupt a scheduled task that is currently running
php artisan schedule:interrupt
Task Hooks (Before & After): Use before(), after(), onSuccess(), and onFailure() to execute logic around task execution.
📄routes/console.php
PHP
<?php

use Illuminate\Support\Facades\Schedule;
use Illuminate\Support\Facades\Log;

Schedule::command('app:generate-report')
    ->daily()
    ->before(function () {
        Log::info('Report generation started.');
    })
    ->after(function () {
        Log::info('Report generation completed.');
    })
    ->onSuccess(function () {
        // Notify team of success
        Notification::route('slack', env('SLACK_WEBHOOK_URL'))
            ->notify(new ReportSuccessNotification);
    })
    ->onFailure(function () {
        // Alert on failure
        Log::error('Report generation failed!');
    })
    ->pingBefore('https://hc-ping.com/your-uuid/start')  // Ping before run (Healthchecks.io)
    ->thenPing('https://hc-ping.com/your-uuid');          // Ping after run