Master Laravel Task Scheduling: From Cron Setup to Advanced Constraints

This guide explains how to replace per‑task Cron entries with Laravel's unified scheduler, showing how to configure a single Cron line, define schedules in app/Console/Kernel.php using closures, invokable objects, Artisan commands, queue jobs, shell commands, and how to fine‑tune frequency, time windows, environment, timezone, overlapping, single‑server execution, background processing, maintenance mode, output handling, hooks, and URL pings.

Laravel Tech Community
Laravel Tech Community
Laravel Tech Community
Master Laravel Task Scheduling: From Cron Setup to Advanced Constraints

Introduction

Previously you might have created a separate Cron entry on the server for each scheduled task, which quickly becomes unwieldy because the schedules are not part of the source code and you must SSH into the server to add entries. Laravel's command scheduler lets you define all schedules inside the application, requiring only a single Cron entry on the server.

Starting the Scheduler

Add the following Cron line to your server (or use a service like Laravel Forge to manage it):

* * * * * cd /path-to-your-project && php artisan schedule:run >> /dev/null 2>&1

This runs the Laravel scheduler every minute; when schedule:run is executed, Laravel evaluates the tasks defined in app/Console/Kernel.php.

Defining Schedules

All scheduled tasks are defined in the schedule method of the App\Console\Kernel class. The example below schedules a daily closure that clears a table:

<?php
namespace App\Console;

use Illuminate\Support\Facades\DB;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;

class Kernel extends ConsoleKernel
{
    protected $commands = [];

    protected function schedule(Schedule $schedule)
    {
        $schedule->call(function () {
            DB::table('recent_users')->delete();
        })->daily();
    }
}

Invokable Objects

You can also schedule an invokable class instead of a closure:

$schedule->call(new DeleteRecentUsers)->daily();

Artisan Command Scheduling

Schedule Artisan commands directly:

$schedule->command('emails:send --force')->daily();
$schedule->command(EmailsCommand::class, ['--force'])->daily();

Queue Job Scheduling

Use the job method to dispatch a queued job without writing a closure:

$schedule->job(new Heartbeat)->everyFiveMinutes();
$schedule->job(new Heartbeat, 'heartbeats')->everyFiveMinutes();

Shell Command Scheduling

Run arbitrary shell commands with exec:

$schedule->exec('node /home/forge/script.js')->daily();

Scheduling Frequency

Laravel provides many helper methods to set the execution interval: ->cron('* * * * *') – custom Cron expression

->everyMinute()
->everyFiveMinutes()
->hourly()
->daily()
->dailyAt('13:00')
->twiceDaily(1, 13)
->weekly()
->weeklyOn(1, '8:00')
->monthly()
->monthlyOn(4, '15:00')
->quarterly()
->yearly()
->timezone('America/New_York')

Additional conditional helpers include ->weekdays(), ->weekends(), ->mondays(), ->between($start, $end), ->when(Closure), ->skip(Closure), and ->environments([...]).

Time Range Constraints

Limit execution to a specific time window with between or exclude a window with unlessBetween:

$schedule->command('reminders:send')
    ->hourly()
    ->between('7:00', '22:00');

$schedule->command('reminders:send')
    ->hourly()
    ->unlessBetween('23:00', '4:00');

Conditional Execution

Run a task only when a closure returns true using when, or prevent execution with skip:

$schedule->command('emails:send')
    ->daily()
    ->when(function () { return true; });

$schedule->command('emails:send')
    ->daily()
    ->skip(function () { return true; });

Environment Constraints

Execute a task only in specific environments:

$schedule->command('emails:send')
    ->daily()
    ->environments(['staging', 'production']);

Timezone

Specify the timezone for a task:

$schedule->command('report:generate')
    ->timezone('America/New_York')
    ->at('02:00');

To set a default timezone for all tasks, add a scheduleTimezone method to app/Console/Kernel.php:

protected function scheduleTimezone()
{
    return 'America/Chicago';
}

Be aware of daylight‑saving changes, which can cause tasks to run twice or be skipped.

Avoiding Overlap

Prevent a task from starting if a previous instance is still running:

$schedule->command('emails:send')->withoutOverlapping();

You can set a custom lock expiration (in minutes):

$schedule->command('emails:send')->withoutOverlapping(10);

Single‑Server Execution

When the application runs on multiple servers, ensure a task runs on only one server using onOneServer:

$schedule->command('report:generate')
    ->fridays()
    ->at('17:00')
    ->onOneServer();

Background Tasks

Run long‑running commands in the background so subsequent tasks are not delayed:

$schedule->command('analytics:report')
    ->daily()
    ->runInBackground();

Maintenance Mode

By default, scheduled queue jobs do not run in maintenance mode. To force execution, use evenInMaintenanceMode:

$schedule->command('emails:send')->evenInMaintenanceMode();

Task Output

Direct task output to a file, append to a file, or email it:

$schedule->command('emails:send')
    ->daily()
    ->sendOutputTo($filePath);

$schedule->command('emails:send')
    ->daily()
    ->appendOutputTo($filePath);

$schedule->command('foo')
    ->daily()
    ->sendOutputTo($filePath)
    ->emailOutputTo('[email protected]');

Note: emailOutputTo, sendOutputTo, and appendOutputTo are only available for command and exec tasks.

Task Hooks

Execute custom code before or after a task with before and after:

$schedule->command('emails:send')
    ->daily()
    ->before(function () { /* task is about to start */ })
    ->after(function () { /* task is complete */ });

Ping URLs

Notify external services by pinging a URL before or after a task using pingBefore and thenPing. Conditional variants pingBeforeIf and thenPingIf only ping when a given condition is true:

$schedule->command('emails:send')
    ->daily()
    ->pingBefore($url)
    ->thenPing($url);

$schedule->command('emails:send')
    ->daily()
    ->pingBeforeIf($condition, $url)
    ->thenPingIf($condition, $url);

These methods require the Guzzle HTTP library. Install it via Composer:

composer require guzzlehttp/guzzle
Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Backend Developmenttask schedulingcronLaravelArtisan
Laravel Tech Community
Written by

Laravel Tech Community

Specializing in Laravel development, we continuously publish fresh content and grow alongside the elegant, stable Laravel framework.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.