Managing Scheduled Tasks in Laravel with Forge


Laravel’s task scheduler is one of my favorite features. It lets you define your scheduled tasks in code rather than managing cron entries. Here’s how to set it up properly with Forge.

The Laravel Scheduler

Instead of configuring individual cron jobs, Laravel lets you define all your scheduled tasks in app/Console/Kernel.php:

protected function schedule(Schedule $schedule)
{
    $schedule->command('emails:send')->daily();
    $schedule->command('reports:generate')->weekly();
    $schedule->command('backup:run')->dailyAt('01:00');
}

Setting Up the Scheduler in Forge

When you create a Laravel site in Forge, the scheduler is automatically configured. Forge adds this cron entry to your server:

* * * * * cd /home/forge/your-site.com && php artisan schedule:run >> /dev/null 2>&1

This runs every minute and checks if any scheduled tasks need to execute.

Verifying Your Scheduler

To confirm your scheduler is working:

  1. Add a test task in your Kernel.php:
$schedule->call(function () {
    logger('Scheduler is running');
})->everyMinute();
  1. Wait a minute and check your logs:
tail -f storage/logs/laravel.log

You should see the log entry appearing every minute.

Common Scheduled Tasks

Here are some practical examples:

Database Backups

$schedule->command('backup:run')->daily()->at('02:00');

Cleaning Old Records

$schedule->command('telescope:prune')->daily();
$schedule->command('horizon:snapshot')->everyFiveMinutes();

Generating Reports

$schedule->command('reports:daily')
    ->dailyAt('08:00')
    ->emailOutputOnFailure('admin@example.com');

API Syncing

$schedule->command('sync:orders')
    ->hourly()
    ->withoutOverlapping()
    ->runInBackground();

Task Options

Laravel provides powerful options for scheduled tasks:

Prevent Overlaps: Ensure a task doesn’t run if the previous instance is still running

$schedule->command('heavy:process')
    ->everyFiveMinutes()
    ->withoutOverlapping();

Run in Background: Don’t block other scheduled tasks

$schedule->command('long:task')->runInBackground();

Maintenance Mode: Only run when the application is not in maintenance mode

$schedule->command('api:sync')->hourly()->unlessBetween('1:00', '5:00');

Notifications: Get notified when tasks fail

$schedule->command('critical:task')
    ->daily()
    ->emailOutputOnFailure('alerts@example.com');

Monitoring Scheduled Tasks

Using Forge

Forge doesn’t provide built-in scheduler monitoring, but you can:

  1. Check logs in storage/logs/laravel.log
  2. Use Laravel Telescope to see scheduled task execution
  3. Implement custom monitoring

Health Checks

Add a health check endpoint:

// In your routes/web.php or api.php
Route::get('/health/scheduler', function () {
    $lastRun = Cache::get('scheduler_last_run');

    if (!$lastRun || $lastRun->diffInMinutes(now()) > 5) {
        return response()->json(['status' => 'error'], 500);
    }

    return response()->json(['status' => 'ok']);
});

// In your Console/Kernel.php
$schedule->call(function () {
    Cache::put('scheduler_last_run', now());
})->everyMinute();

Then use a service like Oh Dear, Envoyer, or UptimeRobot to monitor this endpoint.

Debugging Issues

If your scheduled tasks aren’t running:

  1. Check the cron entry: SSH into your server and run crontab -l
  2. Verify permissions: Ensure the forge user can execute your application
  3. Check your timezone: Scheduled tasks use your application’s timezone
  4. Review logs: Check both Laravel logs and system cron logs

Best Practices

  • Always use withoutOverlapping() for long-running tasks
  • Add email notifications for critical tasks
  • Keep tasks small and focused
  • Use queued jobs for heavy processing
  • Monitor task execution in production
  • Test scheduled tasks locally using php artisan schedule:work

The Laravel scheduler combined with Forge’s automatic setup makes managing recurring tasks a breeze.