Mastering Laravel Task Scheduling

· 5 min read

In the past, scheduling tasks on a server required adding multiple entries to the server's crontab configuration. Laravel's Command Scheduler allows you to define your command schedule fluently and expressively within Laravel itself.

Why Use Laravel Scheduler?

Traditional cron has limitations:

  1. Version control: Crontab lives on the server, not in your repository
  2. Environment differences: Dev, staging, and production have different crontabs
  3. Readability: 0 3 * * 1 is cryptic
  4. Testing: You can't unit test crontab entries

Laravel's scheduler solves all of these by moving scheduling logic into your application code.

Enabling the Scheduler

You only need to add one single Cron entry to your server to run the scheduler every minute:

* * * * * cd /path-to-your-project && php artisan schedule:run >> /dev/null 2>&1

That's it. Laravel takes over from there.

Defining Schedules

You define your scheduled tasks in the routes/console.php file (or app/Console/Kernel.php in older Laravel versions).

Scheduling Artisan Commands

use Illuminate\Support\Facades\Schedule;

Schedule::command('emails:send')->daily();

// With arguments
Schedule::command('report:generate --type=sales')->weeklyOn(1, '8:00');

Scheduling Closures

Schedule::call(function () {
    DB::table('recent_users')->delete();
})->daily();

// Named closure for better logging
Schedule::call(function () {
    Cache::flush();
})->daily()->name('cache-flush');

Scheduling Shell Commands

Schedule::exec('node /home/forge/script.js')->daily();

// With output
Schedule::exec('backup.sh')->daily()->sendOutputTo('/var/log/backup.log');

Scheduling Queued Jobs

Schedule::job(new ProcessPodcast)->everyFiveMinutes();

// On specific queue
Schedule::job(new ProcessPodcast, 'podcasts', 'sqs')->everyFiveMinutes();

Schedule Frequencies

Laravel offers a huge variety of frequencies:

->everyMinute()          // Every minute
->everyTwoMinutes()      // Every 2 minutes
->everyFiveMinutes()     // Every 5 minutes
->everyTenMinutes()      // Every 10 minutes
->everyFifteenMinutes()  // Every 15 minutes
->everyThirtyMinutes()   // Every 30 minutes
->hourly()               // Every hour
->hourlyAt(17)           // At minute 17 of every hour
->everyOddHour()         // Every odd hour
->everyTwoHours()        // Every 2 hours
->daily()                // Every day at midnight
->dailyAt('13:00')       // Every day at 1 PM
->twiceDaily(1, 13)      // At 1 AM and 1 PM
->weekly()               // Every Sunday at midnight
->weeklyOn(1, '8:00')    // Every Monday at 8 AM
->monthly()              // First day of month at midnight
->monthlyOn(4, '15:00')  // 4th day of month at 3 PM
->quarterly()            // First day of quarter
->yearly()               // January 1st at midnight
->weekdays()             // Weekdays only
->weekends()             // Weekends only
->sundays()              // Sundays only
->mondays()              // Mondays only

Custom Cron Expressions

Schedule::command('emails:send')->cron('0 */2 * * 1-5');

Preventing Task Overlaps

By default, scheduled tasks will run even if the previous instance is still running. To prevent this:

Schedule::command('emails:send')->withoutOverlapping();

// With custom lock timeout (in minutes)
Schedule::command('emails:send')->withoutOverlapping(10);

Running Tasks on One Server

If your application runs on multiple servers, you don't want the same job running on all of them simultaneously. Use onOneServer():

Schedule::command('report:generate')
    ->fridays()
    ->onOneServer();

Note: This requires a centralized cache driver like Redis or Memcached.

Background Execution

To ensure lengthy tasks don't block the scheduler from starting other tasks, execute them in the background:

Schedule::command('analytics:report')->daily()->runInBackground();

Conditional Scheduling

Run tasks based on conditions:

// Environment-based
Schedule::command('emails:send')
    ->daily()
    ->environments(['production']);

// Callback-based
Schedule::command('emails:send')
    ->daily()
    ->when(function () {
        return config('app.send_emails');
    });

// Skip condition
Schedule::command('emails:send')
    ->daily()
    ->skip(function () {
        return app()->isDownForMaintenance();
    });

Hooks: Before and After

Execute code before or after a task:

Schedule::command('emails:send')
    ->daily()
    ->before(function () {
        Log::info('Starting email send...');
    })
    ->after(function () {
        Log::info('Email send completed.');
    });

Ping URLs

Notify external services when tasks run:

Schedule::command('emails:send')
    ->daily()
    ->pingBefore('https://healthcheck.io/start/xxx')
    ->pingOnSuccess('https://healthcheck.io/complete/xxx')
    ->pingOnFailure('https://healthcheck.io/fail/xxx');

Task Output

Capture and send task output:

// Save to file
Schedule::command('emails:send')
    ->daily()
    ->sendOutputTo('/var/log/emails.log');

// Append to file
Schedule::command('emails:send')
    ->daily()
    ->appendOutputTo('/var/log/emails.log');

// Email output
Schedule::command('report:generate')
    ->weekly()
    ->emailOutputTo('admin@example.com');

// Email only on failure
Schedule::command('report:generate')
    ->weekly()
    ->emailOutputOnFailure('admin@example.com');

Testing Your Schedule

List all scheduled tasks:

php artisan schedule:list

Run the scheduler once (for testing):

php artisan schedule:run

Run a specific scheduled task immediately:

php artisan schedule:test

Monitoring with Laravel Pulse

If you use Laravel Pulse, schedule metrics are automatically captured:

Schedule::command('emails:send')
    ->daily()
    ->name('daily-emails'); // Named tasks show in Pulse

Laravel's scheduler puts the power of automation back into your codebase, making it version-controlled, testable, and easy to manage. No more SSH-ing into servers to edit crontab.

Comments