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