Muhammad Yasir Amin

Loading websiteโ€ฆ

More from Muhammad Yasir Amin

Build Along With Me

Practical walkthroughs on Laravel, React, APIs and deployment workflows.

Subscribe on YouTube

How to Schedule Tasks in Laravel 13 with Cron and the Scheduler

๐Ÿ› ๏ธ Laravel Tutorials5 min read

How to Schedule Tasks in Laravel 13 with Cron and the Scheduler

Muhammad Yasir Amin

Muhammad Yasir Amin

Senior Full Stack Engineerโ€ขNov 7, 2026
How to Schedule Tasks in Laravel 13 with Cron and the Scheduler

To run scheduled tasks in Laravel 13, define them in routes/console.php with the Schedule facade and add one cron entry that runs php artisan schedule:run every minute. Laravel then decides which tasks are due. This guide targets Laravel 13 and PHP 8.3 or newer.

What do you need before you start?

  • A Laravel 13 application on PHP 8.3 or newer.
  • Shell access to the server so you can edit the crontab (or a Windows Task Scheduler job on a local machine).
  • Basic familiarity with Artisan commands.

Step 1: Create a command to schedule

Any Artisan command can be scheduled. Create a small one that removes expired password reset tokens from a custom table, or any cleanup job of your own.

Code
php artisan make:command PruneStaleExports
JavaScript
<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;

class PruneStaleExports extends Command
{
    protected $signature = 'exports:prune {--days=7}';

    protected $description = 'Delete export records older than the given number of days';

    public function handle(): int
    {
        $deleted = DB::table('exports')
            ->where('created_at', '<', now()->subDays((int) $this->option('days')))
            ->delete();

        $this->info("Deleted {$deleted} stale exports.");

        return self::SUCCESS;
    }
}

Step 2: How do you register the schedule?

Since Laravel 11 there is no app/Console/Kernel.php. You declare the schedule in routes/console.php.

PHP
<?php

use Illuminate\Support\Facades\Schedule;

Schedule::command('exports:prune --days=7')
    ->dailyAt('02:30')
    ->timezone('Asia/Karachi')
    ->withoutOverlapping()
    ->onOneServer();

Schedule::call(function () {
    cache()->forget('homepage.stats');
})->hourly();

Schedule::job(new \App\Jobs\SendDigestEmails)->weekdays()->at('08:00');

You can schedule an Artisan command, a closure, a queued job or a shell command (Schedule::exec()). Frequency helpers such as everyMinute(), hourly(), dailyAt(), weeklyOn() and cron('* * * * *') cover most needs.

Step 3: Add the single cron entry

On Linux, open the crontab of the user that owns the project and add this line (change the path):

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

This is the only cron line you will ever need. When you add new tasks, you edit PHP code, not the server.

Step 4: How do you run the scheduler while developing?

Locally you do not need cron. Run the worker command in a terminal and leave it open:

Code
php artisan schedule:work

It calls the scheduler every minute in the foreground. To see what is registered, run:

Code
php artisan schedule:list

Which options prevent common scheduling problems?

  • withoutOverlapping() stops a new run starting while the previous one is still working. It uses a cache lock, so your cache driver must support locks.
  • onOneServer() makes sure only one server runs the task when you deploy several servers. It needs a shared cache store such as Redis or the database, not the array or file store on separate machines.
  • runInBackground() lets long commands run without blocking the other tasks due in the same minute.
  • when() and skip() let you add conditions, for example run only in production.
  • emailOutputOnFailure(), appendOutputTo() and the onFailure() hook help with monitoring.
JavaScript
Schedule::command('reports:build')
    ->hourly()
    ->when(fn () => app()->isProduction())
    ->appendOutputTo(storage_path('logs/reports-schedule.log'))
    ->onFailure(function () {
        logger()->error('reports:build failed');
    });

Common mistakes

  • Putting the schedule in the cron file as many lines. Use one schedule:run line only.
  • Running cron as a different user than the one that owns storage/. Log and cache files then become unwritable.
  • Forgetting the timezone. Without timezone() or the schedule_timezone value in config/app.php, times follow the application timezone.
  • Using onOneServer() with a per-machine cache store.
  • Expecting tasks to run on a machine where the cron job is missing after a deployment or container rebuild.

How do you verify that it works?

  1. Run php artisan schedule:list and confirm your task and its next due time appear.
  2. Run php artisan exports:prune --days=0 manually to check the command itself.
  3. Temporarily change a task to everyMinute() with appendOutputTo(), wait a minute and read the log file.
  4. Run php artisan schedule:test to pick and run a single task on demand.

How do task groups and maintenance mode behave?

When several tasks share the same settings, group them so you do not repeat yourself. Laravel lets you wrap them in a closure with shared constraints.

CSS
Schedule::daily()
    ->onOneServer()
    ->timezone('Asia/Karachi')
    ->group(function () {
        Schedule::command('exports:prune');
        Schedule::command('reports:build');
    });

By default, scheduled tasks do not run while the application is in maintenance mode (php artisan down). If a task must still run, add evenInMaintenanceMode(). This matters for backups and health checks.

How do you test a scheduled command?

Test the command, not the clock. Call it with Artisan in a feature test and assert on the result. This keeps the schedule definition thin and the logic testable.

JavaScript
it('prunes old exports', function () {
    DB::table('exports')->insert(['created_at' => now()->subDays(10)]);

    $this->artisan('exports:prune --days=7')
        ->expectsOutput('Deleted 1 stale exports.')
        ->assertExitCode(0);
});

FAQ

Do I need a queue worker for scheduled tasks?

No, unless the scheduled item is a queued job. A command or closure runs inside the schedule:run process. A job scheduled with Schedule::job() is pushed to the queue and needs a worker.

How often should schedule:run be called?

Every minute. Laravel checks which tasks are due and ignores the rest, so a minute is the finest resolution you get.

How do I run the scheduler in Docker?

Run php artisan schedule:work as a separate long-running container or process, so you do not depend on cron inside the image.

Can I schedule tasks every few seconds?

Yes. Laravel supports sub-minute tasks such as everyFiveSeconds(), but then schedule:run keeps running until the end of the minute, so plan for it when deploying.

References

Article Index & Topic Tags
LaravelSchedulerCronArtisan

YouTube Channel

Build Along With Me

Practical walkthroughs on Laravel, React, APIs and deployment workflows.

Muhammad Yasir Amin
Available for Contracts
Author & Solutions Architect

Senior Full Stack Engineer

Independent Senior Full Stack Engineer working on Laravel, React and Next.js applications, APIs and delivery workflows.

Laravel & PHP ApplicationsReact & Next.js InterfacesREST APIs & IntegrationsDatabase Queries & DebuggingDocker Development WorkflowsClear Scope & Handover
Discuss Your Next Step

What Does Your Application Need Next?

Share your current system, the problem you want to solve and what a useful result looks like. We can identify the right starting point and agree on a practical scope.

Technical Audit

Find the Next Useful Fix

Application Development

Scoped, Reviewable Milestones

Ongoing Support

Agreed Capacity and Priorities

Share your goals, current stack and requirements before the call.