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:
- Add a test task in your
Kernel.php:
$schedule->call(function () {
logger('Scheduler is running');
})->everyMinute();
- 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:
- Check logs in
storage/logs/laravel.log - Use Laravel Telescope to see scheduled task execution
- 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:
- Check the cron entry: SSH into your server and run
crontab -l - Verify permissions: Ensure the
forgeuser can execute your application - Check your timezone: Scheduled tasks use your application’s timezone
- 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.