Laravel's scheduler is a single entry point that reads your defined schedule and decides what actually needs to run — the server-level cron job itself never changes, no matter how many scheduled tasks you add or remove.
1. The one crontab entry
* * * * * cd /path-to-your-project && php artisan schedule:run >> /dev/null 2>&1
This has to run every minute — not because every task fires every minute, but because schedule:run is what checks, each minute, whether anything on the actual schedule is due right now.
2. Defining the schedule — Laravel 11+
In newer Laravel, scheduled tasks are defined in routes/console.php (or via withSchedule() in bootstrap/app.php), not in a Kernel class:
use Illuminate\Support\Facades\Schedule;
Schedule::command('invites:prune')->daily();
Schedule::call(fn () => Cache::flush())->weekly();
Laravel 10 and earlier
Defined inside app/Console/Kernel.php's schedule(Schedule $schedule) method instead — same fluent API, different location. If you're following an older tutorial and can't find Kernel.php in a fresh install, that's the Laravel 11 restructure, not a missing file.
Testing without waiting for cron
php artisan schedule:run # runs whatever is due right now
php artisan schedule:list # shows every scheduled task and its next run time
Overlapping runs
A long-running task can still be executing when the next minute's schedule:run fires. Use ->withoutOverlapping() on tasks that shouldn't run concurrently with themselves — without it, a slow task can end up with multiple copies running at once.
Use schedule:work locally when you do not have cron
For local development, Laravel can keep a foreground scheduler process running:
php artisan schedule:work
That is convenient on a workstation, but the production pattern remains a process manager or the single cron entry invoking schedule:run. Do not leave a terminal-dependent schedule:work process as an accidental production service.
Sub-minute tasks change what schedule:run does
Laravel supports schedules that repeat within a minute. When such tasks are defined, a schedule:run invocation can remain alive for the rest of that minute so it can trigger the sub-minute events. That makes deployment behavior worth thinking about: long-lived scheduler invocations from an old release should be interrupted cleanly when new code is deployed rather than allowed to keep executing stale application code.
withoutOverlapping() uses a cache lock
The overlap guard is not process detection; it relies on Laravel's cache lock. If a server crashes while holding a lock, the lock lives until its configured expiry unless it is cleared. Choose an expiry that is comfortably longer than the legitimate run time but not so long that one abnormal failure suppresses the task all day.
Multiple application servers need onOneServer()
If the same cron entry runs on every web node, every node will evaluate the schedule. For tasks that must execute only once across the cluster, add ->onOneServer() and use a supported shared cache backend so the nodes coordinate through the same atomic lock. withoutOverlapping() prevents one task from overlapping itself; onOneServer() answers the separate question of which server gets to run it.