Skip to content
Tech

Scheduling Tasks in Laravel: A Practical Guide to the Task Scheduler

Replace a messy crontab with Laravel's scheduler: defining tasks, preventing overlaps, running on one server and monitoring jobs that silently fail.

Editorial illustration of a large clock face overlaid with server racks and calendar grid lines
Editorial illustration of a large clock face overlaid with server racks and calendar grid lines
In this article

    Almost every web application needs work that runs on a timetable: sending daily digests, cleaning up expired tokens, generating reports or syncing data with a third-party service. Laravel's task scheduler lets you define all of this in code, under version control, with a single cron entry on the server.

    The single cron entry

    Instead of adding a separate crontab line for every job, you add one line that runs the scheduler every minute. Laravel then decides which tasks are due.

    * * * * * cd /var/www/app && php artisan schedule:run >> /dev/null 2>&1

    For local development, php artisan schedule:work runs the scheduler in the foreground so you can test without touching cron.

    Defining scheduled tasks

    In recent Laravel versions, scheduled tasks live in routes/console.php. You can schedule Artisan commands, queued jobs or closures, and choose from expressive frequency helpers.

    use Illuminate\Support\Facades\Schedule;
    
    Schedule::command('reports:daily')->dailyAt('06:30');
    Schedule::job(new PruneExpiredTokens)->hourly();
    Schedule::command('sync:inventory')->everyFifteenMinutes();

    Remember to set the application timezone, or pass ->timezone('Asia/Kolkata') on individual tasks, so that a report meant for 6:30 in the morning does not arrive at midnight.

    Preventing overlapping runs

    If a task occasionally takes longer than its interval, two copies can run at once and step on each other. Adding ->withoutOverlapping() uses a cache lock so a new run is skipped while the previous one is still going. Make sure your cache driver supports atomic locks, such as Redis or the database driver.

    Running on one server only

    When you scale to several application servers, each will run the scheduler. For tasks that must execute once, such as sending emails, chain ->onOneServer(). All servers need to share the same central cache for this lock to work.

    Keep tasks thin and push heavy work to queues

    The scheduler process should start work, not do all of it. If a nightly task needs to process thousands of records, have it dispatch queued jobs in chunks. This keeps the scheduler responsive, lets you retry failed pieces individually and spreads load across your queue workers.

    • Use runInBackground() for long commands so other due tasks are not delayed.
    • Chunk large datasets with chunkById to keep memory usage predictable.
    • Make tasks idempotent so running them twice by accident causes no harm.

    Capturing output and failures

    Scheduled tasks fail quietly by default; nobody is watching the terminal at three in the morning. Send output somewhere useful and react to failures.

    Schedule::command('reports:daily')
        ->dailyAt('06:30')
        ->appendOutputTo(storage_path('logs/reports.log'))
        ->onFailure(fn () => Log::error('Daily report failed'));

    For important jobs, use a heartbeat monitoring service: the task pings a URL when it finishes, and you are alerted if the ping does not arrive on time. Laravel provides pingOnSuccess and thenPing helpers for exactly this.

    Testing and inspecting the schedule

    Run php artisan schedule:list to see every task and when it will next run. This is a quick sanity check after deployments. For the logic itself, keep it inside commands or jobs that you can unit test independently of the timetable.

    Maintenance mode and environments

    Scheduled tasks do not run while the application is in maintenance mode unless you add ->evenInMaintenanceMode(). You can also restrict tasks to certain environments with ->environments(['production']), which prevents a staging server from emailing real customers.

    Common scheduler mistakes

    A few problems appear again and again. Developers forget to add the cron entry on a new server, so nothing runs after a migration. Tasks rely on a local file cache while running on several servers, so locks do not work. A long-running task blocks others because it was not sent to the background. Reviewing schedule:list and your monitoring alerts after every infrastructure change catches most of these before customers notice.

    Wrapping up

    The Laravel scheduler turns scattered cron lines into readable, reviewable code. Set the timezone, prevent overlaps, use one-server locks when you scale, push heavy work to queues and, above all, monitor your tasks so that a silent failure does not go unnoticed for weeks.

    Written by Raj

    Founder & Editor

    I write HorizonDesk: practical, no-hype articles on technology, money, business and the vehicles we drive. Everything here is something I’ve researched, tested or wanted explained clearly myself.