Defining Scheduled Tasks

Instead of a crontab line per task (cron and crontab), the schedule lives in routes/console.php. Schedule::command(), job(), call() and exec() run commands, jobs, closures and shell commands; shop:reconcile is a closure command that sleeps 8 seconds:

The bookshop's schedule in routes/console.phpPHP
Schedule::job(new RebuildBestsellers)->hourly()->onOneServer();
Schedule::command('shop:reconcile')->everyMinute()->withoutOverlapping(10);
Schedule::command('shop:invoices')->monthlyOn(1, '02:00')->onOneServer();
Schedule::command('queue:prune-failed --hours=168')->dailyAt('03:15');
Schedule::command('queue:prune-batches --hours=48')->daily();
Schedule::command('horizon:snapshot')->everyFiveMinutes();
Schedule::call(fn () => Log::info('scheduler heartbeat'))->name('heartbeat')->everyMinute();
Output
$ php artisan schedule:list
  0   * * * *  App\Jobs\RebuildBestsellers ..... Next Due: 19 minutes from now
  *   * * * *  php artisan shop:reconcile ...... Next Due: 15 seconds from now
  0   2 1 * *  php artisan shop:invoices ........... Next Due: 1 week from now
  15  3 * * *  php artisan queue:prune-failed --hours=168  Next Due: 18 hours from now
  0   0 * * *  php artisan queue:prune-batches --hours=48  Next Due: 15 hours from now
  */5 * * * *  php artisan horizon:snapshot ..... Next Due: 4 minutes from now
  *   * * * *  heartbeat ....................... Next Due: 15 seconds from now

Frequencies run from everySecond() to yearlyOn(); weekdays(), between('8:00', '17:00'), when() and environments('production') narrow them. Times follow app.timezone unless you set schedule_timezone; zones with daylight saving time can run a task twice or skip it. withoutOverlapping() skips a run while the last one continues, via a cache lock that expires after 24 hours unless you give minutes. A second schedule:run, started three seconds into a first one busy with the reconcile, ran only horizon:snapshot and heartbeat. Due tasks run one after another, so the first run's snapshot waited eight seconds; runInBackground() keeps a slow command() or exec() task from holding up the rest.