스케줄링

번역일: 2026년 6월 27일

스케줄링

소개

예전에는 서버에서 주기적으로 실행해야 하는 작업마다 cron 설정을 직접 추가해야 했습니다. 그러다 보면 스케줄 설정이 소스 코드 밖으로 흩어지고, SSH로 서버에 접속해서 crontab을 열어봐야만 현재 등록된 작업을 확인할 수 있는 번거로움이 생깁니다.

Laravel의 커맨드 스케줄러는 이 문제를 깔끔하게 해결합니다. 애플리케이션 코드 안에서 스케줄을 유연하고 표현력 있게 정의할 수 있으며, 서버에는 단 하나의 cron 항목만 등록하면 됩니다. 스케줄은 보통 routes/console.php 파일에 정의합니다.

스케줄 정의하기

모든 스케줄 작업은 routes/console.php 파일에 정의합니다. 아래 예시를 살펴보겠습니다. 매일 자정에 클로저를 실행해 데이터베이스 테이블의 데이터를 삭제합니다.

<?php use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Schedule; Schedule::call(function () { DB::table('recent_users')->delete(); })->daily();

클로저 외에도 인보커블 객체를 사용할 수 있습니다. 인보커블 객체는 __invoke 메서드를 가진 단순한 PHP 클래스입니다.

Schedule::call(new DeleteRecentUsers)->daily();

routes/console.php 파일을 커맨드 정의 전용으로만 사용하고 싶다면, bootstrap/app.php에서 withSchedule 메서드를 사용해 스케줄을 정의할 수도 있습니다.

use Illuminate\Console\Scheduling\Schedule; ->withSchedule(function (Schedule $schedule) { $schedule->call(new DeleteRecentUsers)->daily(); })

등록된 스케줄 목록과 다음 실행 예정 시각을 확인하려면 다음 Artisan 커맨드를 사용하세요.

php artisan schedule:list

Artisan 커맨드 스케줄링

클로저 외에도 Artisan 커맨드와 시스템 커맨드를 스케줄링할 수 있습니다. command 메서드에 커맨드 이름 또는 클래스명을 전달하면 됩니다.

클래스명을 사용하는 경우, 커맨드 실행 시 전달할 추가 인자를 배열로 넘길 수 있습니다.

use App\Console\Commands\SendEmailsCommand; use Illuminate\Support\Facades\Schedule; Schedule::command('emails:send Taylor --force')->daily(); Schedule::command(SendEmailsCommand::class, ['Taylor', '--force'])->daily();

클로저로 정의된 Artisan 커맨드 스케줄링

클로저로 정의한 Artisan 커맨드는 커맨드 정의 뒤에 스케줄 메서드를 바로 체이닝할 수 있습니다.

Artisan::command('delete:recent-users', function () { DB::table('recent_users')->delete(); })->purpose('최근 사용자 삭제')->daily();

클로저 커맨드에 인자를 전달해야 한다면 schedule 메서드에 제공하세요.

Artisan::command('emails:send {user} {--force}', function ($user) { // ... })->purpose('지정한 사용자에게 이메일 발송')->schedule(['Taylor', '--force'])->daily();

큐 Job 스케줄링

job 메서드를 사용하면 큐 Job을 간편하게 스케줄링할 수 있습니다. 클로저로 직접 큐에 디스패치하는 방식보다 훨씬 간결합니다.

use App\Jobs\Heartbeat; use Illuminate\Support\Facades\Schedule; Schedule::job(new Heartbeat)->everyFiveMinutes();

job 메서드의 두 번째, 세 번째 인자로 큐 이름과 큐 커넥션을 지정할 수 있습니다.

use App\Jobs\Heartbeat; use Illuminate\Support\Facades\Schedule; // "sqs" 커넥션의 "heartbeats" 큐로 Job 디스패치 Schedule::job(new Heartbeat, 'heartbeats', 'sqs')->everyFiveMinutes();

셸 커맨드 스케줄링

운영체제 명령어를 실행하려면 exec 메서드를 사용하세요.

use Illuminate\Support\Facades\Schedule; Schedule::exec('node /home/forge/script.js')->daily();

스케줄 실행 주기 옵션

다양한 실행 주기를 작업에 지정할 수 있습니다.

메서드설명
->cron('* * * * *');커스텀 cron 표현식으로 실행
->everySecond();매 초 실행
->everyTwoSeconds();2초마다 실행
->everyFiveSeconds();5초마다 실행
->everyTenSeconds();10초마다 실행
->everyFifteenSeconds();15초마다 실행
->everyTwentySeconds();20초마다 실행
->everyThirtySeconds();30초마다 실행
->everyMinute();매 분 실행
->everyTwoMinutes();2분마다 실행
->everyThreeMinutes();3분마다 실행
->everyFourMinutes();4분마다 실행
->everyFiveMinutes();5분마다 실행
->everyTenMinutes();10분마다 실행
->everyFifteenMinutes();15분마다 실행
->everyThirtyMinutes();30분마다 실행
->hourly();매 시간 실행
->hourlyAt(17);매 시간 17분에 실행
->everyOddHour($minutes = 0);홀수 시간마다 실행
->everyTwoHours($minutes = 0);2시간마다 실행
->everyThreeHours($minutes = 0);3시간마다 실행
->everyFourHours($minutes = 0);4시간마다 실행
->everySixHours($minutes = 0);6시간마다 실행
->daily();매일 자정(00:00)에 실행
->dailyAt('13:00');매일 13:00에 실행
->twiceDaily(1, 13);매일 1:00, 13:00에 실행
->twiceDailyAt(1, 13, 15);매일 1:15, 13:15에 실행
->weekly();매주 일요일 00:00에 실행
->weeklyOn(1, '8:00');매주 월요일 8:00에 실행
->monthly();매월 1일 00:00에 실행
->monthlyOn(4, '15:00');매월 4일 15:00에 실행
->twiceMonthly(1, 16, '13:00');매월 1일, 16일 13:00에 실행
->lastDayOfMonth('15:00');매월 마지막 날 15:00에 실행
->quarterly();매 분기 첫날 00:00에 실행
->quarterlyOn(4, '14:00');매 분기 4일 14:00에 실행
->yearly();매년 1월 1일 00:00에 실행
->yearlyOn(6, 1, '17:00');매년 6월 1일 17:00에 실행
->timezone('America/New_York');작업의 타임존 설정

이 메서드들은 추가 제약 조건과 조합해 더욱 세밀한 스케줄을 만들 수 있습니다. 예를 들어, 매주 월요일 오후 1시에 한 번 실행하거나 평일 오전 8시~오후 5시 사이에 매 시간 실행하도록 설정할 수 있습니다.

use Illuminate\Support\Facades\Schedule; // 매주 월요일 오후 1시에 실행 Schedule::call(function () { // ... })->weekly()->mondays()->at('13:00'); // 평일 오전 8시~오후 5시에 매 시간 실행 Schedule::command('foo') ->weekdays() ->hourly() ->timezone('America/Chicago') ->between('8:00', '17:00');

추가로 사용할 수 있는 제약 조건은 다음과 같습니다.

메서드설명
->weekdays();평일(월~금)에만 실행
->weekends();주말(토, 일)에만 실행
->sundays();일요일에만 실행
->mondays();월요일에만 실행
->tuesdays();화요일에만 실행
->wednesdays();수요일에만 실행
->thursdays();목요일에만 실행
->fridays();금요일에만 실행
->saturdays();토요일에만 실행
->days(array|mixed);특정 요일에만 실행
->between($startTime, $endTime);지정 시간 범위 안에서만 실행
->unlessBetween($startTime, $endTime);지정 시간 범위 안에서는 실행하지 않음
->when(Closure);조건(클로저)이 true일 때만 실행
->environments($env);특정 환경에서만 실행

요일 제약

days 메서드로 실행할 요일을 지정할 수 있습니다. 아래 예시는 매주 일요일(0)과 수요일(3)에 매 시간 실행합니다.

use Illuminate\Support\Facades\Schedule; Schedule::command('emails:send') ->hourly() ->days([0, 3]);

숫자 대신 Schedule 클래스의 상수를 사용하면 가독성이 더 좋습니다.

use Illuminate\Support\Facades; use Illuminate\Console\Scheduling\Schedule; Facades\Schedule::command('emails:send') ->hourly() ->days([Schedule::SUNDAY, Schedule::WEDNESDAY]);

시간 범위 제약

between 메서드로 하루 중 실행 가능한 시간 범위를 제한할 수 있습니다.

Schedule::command('emails:send')
    ->hourly()
    ->between('7:00', '22:00');

반대로 특정 시간 범위에서 실행을 제외하려면 unlessBetween을 사용하세요.

Schedule::command('emails:send')
    ->hourly()
    ->unlessBetween('23:00', '4:00');

조건(Truth Test) 제약

when 메서드는 클로저가 true를 반환할 때만 작업을 실행합니다.

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

skipwhen의 반대입니다. 클로저가 true를 반환하면 해당 작업을 건너뜁니다.

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

when을 여러 번 체이닝하면 모든 조건이 true일 때만 실행됩니다.

환경 제약

environments 메서드를 사용하면 특정 환경(APP_ENV 환경 변수)에서만 작업이 실행됩니다.

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

타임존

timezone 메서드로 스케줄 작업의 시간 해석에 사용할 타임존을 지정할 수 있습니다.

use Illuminate\Support\Facades\Schedule; Schedule::command('report:generate') ->timezone('America/New_York') ->at('2:00')

모든 스케줄에 동일한 타임존을 반복 지정하는 경우, app 설정 파일에 schedule_timezone 옵션을 추가하면 편리합니다.

'timezone' => 'UTC',

'schedule_timezone' => 'America/Chicago',

WARNING

일부 타임존은 일광절약시간(DST)을 사용합니다. DST 전환이 발생하면 같은 작업이 두 번 실행되거나 아예 실행되지 않을 수 있습니다. 가능하면 타임존 기반 스케줄 설정을 피하는 것이 좋습니다.

작업 중복 실행 방지

기본적으로 이전 작업이 아직 실행 중이더라도 스케줄된 작업은 다시 실행됩니다. 중복 실행을 막으려면 withoutOverlapping 메서드를 사용하세요.

use Illuminate\Support\Facades\Schedule; Schedule::command('emails:send')->withoutOverlapping();

이 예시에서는 emails:send 커맨드가 실행 중이 아닐 때만 매 분 실행됩니다. withoutOverlapping은 실행 시간이 일정하지 않은 작업에 특히 유용합니다.

잠금이 만료되기까지의 시간(분)을 직접 지정할 수도 있습니다. 기본값은 24시간입니다.

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

내부적으로 withoutOverlapping은 애플리케이션의 캐시를 이용해 잠금을 관리합니다. 서버 장애 등으로 작업이 멈춰 잠금이 해제되지 않는 경우, schedule:clear-cache Artisan 커맨드로 캐시 잠금을 직접 해제할 수 있습니다.

단일 서버에서 작업 실행

WARNING

이 기능을 사용하려면 애플리케이션의 기본 캐시 드라이버가 database, memcached, dynamodb, redis 중 하나여야 합니다. 또한 모든 서버가 동일한 중앙 캐시 서버와 통신해야 합니다.

여러 서버에서 스케줄러가 동시에 실행되는 환경이라면, 특정 작업을 단 하나의 서버에서만 실행하도록 제한할 수 있습니다. 예를 들어, 매주 금요일 밤에 리포트를 생성하는 작업이 3대의 워커 서버 모두에서 실행되면 리포트가 3개나 생성되는 문제가 발생합니다.

이를 방지하려면 onOneServer 메서드를 사용하세요. 가장 먼저 잠금을 획득한 서버만 해당 작업을 실행합니다.

use Illuminate\Support\Facades\Schedule; Schedule::command('report:generate') ->fridays() ->at('17:00') ->onOneServer();

단일 서버 Job에 이름 지정

같은 Job을 다른 파라미터로 여러 번 등록하면서 각각 단일 서버에서만 실행하고 싶다면, name 메서드로 각 스케줄에 고유한 이름을 부여하세요.

Schedule::job(new CheckUptime('https://laravel.com')) ->name('check_uptime:laravel.com') ->everyFiveMinutes() ->onOneServer(); Schedule::job(new CheckUptime('https://vapor.laravel.com')) ->name('check_uptime:vapor.laravel.com') ->everyFiveMinutes() ->onOneServer();

클로저 스케줄도 단일 서버 실행을 사용하려면 반드시 이름을 지정해야 합니다.

Schedule::call(fn () => User::resetApiRequestCount()) ->name('reset-api-request-count') ->daily() ->onOneServer();

백그라운드 작업

기본적으로 같은 시각에 예약된 여러 작업은 정의된 순서대로 순차 실행됩니다. 실행 시간이 긴 작업이 있으면 이후 작업이 예상보다 늦게 시작될 수 있습니다. 작업들을 동시에 실행하려면 runInBackground 메서드를 사용하세요.

use Illuminate\Support\Facades\Schedule; Schedule::command('analytics:report') ->daily() ->runInBackground();

WARNING

runInBackgroundcommand 또는 exec 메서드로 등록한 작업에서만 사용할 수 있습니다.

점검(Maintenance) 모드

애플리케이션이 점검 모드일 때는 스케줄 작업이 실행되지 않습니다. 서버 유지보수 중에 작업이 끼어드는 것을 방지하기 위해서입니다. 점검 모드 중에도 작업을 강제로 실행해야 한다면 evenInMaintenanceMode를 사용하세요.

Schedule::command('emails:send')->evenInMaintenanceMode();

스케줄 그룹

설정이 유사한 스케줄 작업이 여러 개라면, 그룹 기능을 활용해 공통 설정을 한 번만 지정할 수 있습니다. 코드가 간결해지고 관련 작업 간 일관성도 유지됩니다.

공통 설정 메서드를 체이닝한 뒤 group 메서드에 클로저를 전달하면, 해당 클로저 안에서 정의한 작업 모두에 설정이 적용됩니다.

use Illuminate\Support\Facades\Schedule; Schedule::daily() ->onOneServer() ->timezone('America/New_York') ->group(function () { Schedule::command('emails:send --force'); Schedule::command('emails:prune'); });

스케줄러 실행하기

스케줄 작업을 정의했다면, 이제 서버에서 실제로 실행하는 방법을 알아보겠습니다. schedule:run Artisan 커맨드는 등록된 모든 스케줄 작업을 서버의 현재 시각과 비교해 실행 여부를 결정합니다.

Laravel 스케줄러를 사용할 때 서버에 등록해야 하는 cron은 단 하나입니다. cron 설정이 익숙하지 않다면 Laravel Forge 같은 서비스를 활용하면 편리합니다.

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

1분 미만 단위 작업

대부분의 운영체제에서 cron은 최소 1분 간격으로만 실행됩니다. 하지만 Laravel 스케줄러는 초 단위까지 더 짧은 주기로 작업을 실행할 수 있습니다.

use Illuminate\Support\Facades\Schedule; Schedule::call(function () { DB::table('recent_users')->delete(); })->everySecond();

1분 미만 단위 작업이 정의된 경우, schedule:run 커맨드는 즉시 종료하지 않고 현재 분이 끝날 때까지 계속 실행되면서 해당 작업들을 호출합니다.

1분 미만 단위 작업이 예상보다 오래 걸리면 이후 작업 실행이 지연될 수 있습니다. 따라서 이런 작업은 실제 처리를 큐 Job이나 백그라운드 커맨드에 위임하는 것을 권장합니다.

use App\Jobs\DeleteRecentUsers; Schedule::job(new DeleteRecentUsers)->everyTenSeconds(); Schedule::command('users:delete')->everyTenSeconds()->runInBackground();

1분 미만 작업 중단하기

1분 미만 단위 작업이 있으면 schedule:run은 해당 분 내내 실행됩니다. 애플리케이션 배포 시 이미 실행 중인 schedule:run 프로세스가 이전 코드를 계속 사용하는 문제가 생길 수 있으므로, 배포 스크립트에 schedule:interrupt 커맨드를 추가해 실행 중인 프로세스를 중단시키세요.

php artisan schedule:interrupt

로컬에서 스케줄러 실행하기

로컬 개발 환경에서는 cron을 직접 등록하는 대신 schedule:work Artisan 커맨드를 사용하세요. 이 커맨드는 포그라운드에서 실행되면서 Ctrl+C로 종료할 때까지 매 분 스케줄러를 호출합니다. 1분 미만 단위 작업이 있으면 해당 분 내에서도 계속 작업을 처리합니다.

php artisan schedule:work

작업 출력

스케줄 작업이 생성하는 출력을 다루는 몇 가지 편리한 메서드가 있습니다. sendOutputTo 메서드로 출력을 파일에 저장할 수 있습니다.

use Illuminate\Support\Facades\Schedule; Schedule::command('emails:send') ->daily() ->sendOutputTo($filePath);

기존 파일에 출력을 이어쓰려면 appendOutputTo를 사용하세요.

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

emailOutputTo 메서드로 출력 결과를 이메일로 받을 수 있습니다. 이 기능을 사용하기 전에 Laravel의 이메일 서비스를 먼저 설정해야 합니다.

Schedule::command('report:generate')
    ->daily()
    ->sendOutputTo($filePath)
    ->emailOutputTo('taylor@example.com');

작업이 0이 아닌 종료 코드로 실패한 경우에만 이메일을 받고 싶다면 emailOutputOnFailure를 사용하세요.

Schedule::command('report:generate')
    ->daily()
    ->emailOutputOnFailure('taylor@example.com');

WARNING

emailOutputTo, emailOutputOnFailure, sendOutputTo, appendOutputTo 메서드는 command 또는 exec 메서드로 등록한 작업에서만 사용할 수 있습니다.

작업 훅

beforeafter 메서드로 작업 실행 전후에 코드를 실행할 수 있습니다.

use Illuminate\Support\Facades\Schedule; Schedule::command('emails:send') ->daily() ->before(function () { // 작업 실행 직전... }) ->after(function () { // 작업 실행 직후... });

onSuccessonFailure 메서드는 작업 성공 또는 실패 시 실행할 코드를 지정합니다. 0이 아닌 종료 코드로 종료되면 실패로 간주됩니다.

Schedule::command('emails:send') ->daily() ->onSuccess(function () { // 작업 성공 시... }) ->onFailure(function () { // 작업 실패 시... });

커맨드 출력 결과가 있다면, 훅 클로저의 $output 인자를 Illuminate\Support\Stringable 타입으로 타입힌트해 출력값에 접근할 수 있습니다.

use Illuminate\Support\Stringable; Schedule::command('emails:send') ->daily() ->onSuccess(function (Stringable $output) { // 작업 성공 시... }) ->onFailure(function (Stringable $output) { // 작업 실패 시... });

URL 핑(Ping)

pingBeforethenPing 메서드를 사용하면 작업 실행 전후에 지정한 URL을 자동으로 핑할 수 있습니다. Envoyer 같은 외부 서비스에 작업 시작/완료를 알릴 때 유용합니다.

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

작업 성공 또는 실패 시에만 핑하려면 pingOnSuccesspingOnFailure를 사용하세요.

Schedule::command('emails:send')
    ->daily()
    ->pingOnSuccess($successUrl)
    ->pingOnFailure($failureUrl);

조건이 true일 때만 핑하려면 아래 조건부 메서드를 사용할 수 있습니다.

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

Schedule::command('emails:send')
    ->daily()
    ->pingOnSuccessIf($condition, $successUrl)
    ->pingOnFailureIf($condition, $failureUrl);

이벤트

Laravel은 스케줄 실행 과정에서 다양한 이벤트를 디스패치합니다. 아래 이벤트들에 대해 리스너를 등록할 수 있습니다.

이벤트 클래스
Illuminate\Console\Events\ScheduledTaskStarting
Illuminate\Console\Events\ScheduledTaskFinished
Illuminate\Console\Events\ScheduledBackgroundTaskFinished
Illuminate\Console\Events\ScheduledTaskSkipped
Illuminate\Console\Events\ScheduledTaskFailed

이 문서는 Laravel 공식 문서(MIT)를 한국 개발자를 위해 번역·재구성한 것입니다.

번역일: 2026년 6월 27일