Updated June 2026. Tested on Laravel 13 and PHP 8.4. Scheduling moved into routes/console.php from Laravel 11; the old Console Kernel is gone.
Every app eventually needs to do something on a timer: send reminder emails, clean up old rows, generate a nightly report. The old way was to litter the server's crontab with one line per task. Laravel flips that around. You add a single cron entry, and then schedule everything from your code, where it lives in version control with the rest of the app.
A quick cron refresher
Cron is the task scheduler built into Unix systems. A cron entry has five time fields and a command.
* * * * * command-to-run
The fields are minute, hour, day of month, month and day of week. An asterisk means "every". So * * * * * runs every minute, and 30 12 * * * runs at 12:30 each day. That is all you need to know here, because Laravel takes over the scheduling itself.
Write a command
Generate an artisan command for the work you want to run. Say we send a birthday message to users.
php artisan make:command SendBirthdayGreetings
That creates app/Console/Commands/SendBirthdayGreetings.php. Give it a signature and put the logic in handle.
namespace App\Console\Commands;
use App\Models\User;
use Illuminate\Console\Command;
class SendBirthdayGreetings extends Command
{
protected $signature = 'users:birthday-greetings';
protected $description = 'Send a birthday message to users whose birthday is today';
public function handle(): int
{
User::whereRaw('DATE_FORMAT(birth_date, "%m-%d") = ?', [now()->format('m-d')])
->each(function (User $user) {
// send the message
});
$this->info('Birthday greetings sent.');
return self::SUCCESS;
}
}
In current Laravel you do not register commands anywhere. Anything in app/Console/Commands is discovered automatically, so you can run it straight away.
php artisan users:birthday-greetings
Schedule it
Scheduling lives in routes/console.php. Use the Schedule facade to say when each command runs.
use Illuminate\Support\Facades\Schedule;
Schedule::command('users:birthday-greetings')->daily();
There is a frequency method for almost anything you need.
Schedule::command('reports:generate')->hourly();
Schedule::command('cache:prune')->dailyAt('09:30');
Schedule::command('newsletter:send')->weeklyOn(1, '08:00'); // Mondays at 08:00
Schedule::command('backup:run')->monthly();
Schedule::command('metrics:sync')->cron('*/5 * * * *'); // every 5 minutes
You can also constrain when a task runs with a condition.
Schedule::command('orders:settle')
->daily()
->when(fn () => Feature::active('auto-settle'));
Check what is scheduled and when it will next run.
php artisan schedule:list
The single cron entry
For all of this to fire, the server needs one cron entry that runs Laravel's scheduler every minute. Add it with crontab -e.
* * * * * cd /path/to/your/app && php artisan schedule:run >> /dev/null 2>&1
That one line is the only thing in the server's crontab. Laravel's schedule:run wakes up each minute, checks which of your scheduled tasks are due, and runs them. On a server with systemd, a timer that calls schedule:run every minute does the same job, which is the approach the DigitalOcean deploy guide uses.
The win is that your schedule is code. It is reviewed, version controlled, and the same on every environment, instead of being hidden in a crontab somewhere on one server.
More in the scheduling series: building and running a scheduled task, preventing scheduled tasks from overlapping, and the properties of a scheduled task. Questions welcome in the comments.
All comments ()
No comments yet
Be the first to leave a comment on this post.