본문 바로가기

작업 스케줄링

업데이트됨

번역일: 2026년 10월 9일

이 페이지는 원문이 업데이트되어 번역이 갱신되었습니다.

원문 수정
2026년 10월 7일
번역 갱신
2026년 10월 9일

작업 스케줄링

소개

서버에서 예약 실행할 작업마다 cron 항목을 작성해 본 적이 있을 것입니다. 하지만 이 방식은 일정이 소스 버전 관리에서 벗어나고, 기존 항목을 확인하거나 추가할 때마다 SSH로 서버에 접속해야 하므로 관리가 번거로워집니다.

Laravel의 명령 스케줄러는 서버의 예약 작업을 관리하는 새로운 방법을 제공합니다. 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(); })

예약 작업 목록과 각 작업의 다음 실행 시각은 schedule:list Artisan 명령으로 확인할 수 있습니다.

php artisan schedule:list

Artisan 명령 스케줄링

클로저뿐 아니라 Artisan 명령과 시스템 명령도 예약할 수 있습니다. 예를 들어 command 메서드에 명령 이름이나 클래스를 지정해 Artisan 명령을 예약합니다.

클래스 이름으로 Artisan 명령을 예약할 때는 실행 시 전달할 추가 명령줄 인수를 배열로 지정할 수 있습니다.

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('Delete recent users')->daily();

클로저 명령에 인수를 전달해야 한다면 schedule 메서드에 지정합니다.

Artisan::command('emails:send {user} {--force}', function ($user) { // ... })->purpose('Send emails to the specified user')->schedule(['Taylor', '--force'])->daily();

큐 Job 스케줄링

job 메서드로 큐 Job을 예약할 수 있습니다. Job을 큐에 넣기 위해 call로 클로저를 작성하지 않아도 되므로 편리합니다.

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

job의 선택적인 두 번째와 세 번째 인수로 Job을 넣을 큐 이름과 큐 연결을 지정할 수 있습니다.

use App\Jobs\Heartbeat; use Illuminate\Support\Facades\Schedule; // Dispatch the job to the "heartbeats" queue on the "sqs" connection... 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();매일 자정에 실행합니다.
->dailyAt('13:00');매일 13:00에 실행합니다.
->twiceDaily(1, 13);매일 1:00과 13:00에 실행합니다.
->twiceDailyAt(1, 13, 15);매일 1:15와 13:15에 실행합니다.
->daysOfMonth([1, 10, 20]);매월 지정한 날짜에 실행합니다.
->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();매년 첫날 00:00에 실행합니다.
->yearlyOn(6, 1, '17:00');매년 6월 1일 17:00에 실행합니다.
->timezone('America/New_York');작업의 시간대를 설정합니다.

이 메서드에 추가 제약을 결합하면 특정 요일에만 실행하는 등 일정을 더 세밀하게 조정할 수 있습니다. 예를 들어 매주 월요일에 명령을 실행하도록 예약할 수 있습니다.

use Illuminate\Support\Facades\Schedule; // Run once per week on Monday at 1 PM... Schedule::call(function () { // ... })->weekly()->mondays()->at('13:00'); // Run hourly from 8 AM to 5 PM on weekdays... 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);조건 검사 결과에 따라 실행을 제한합니다.
->environments($env);지정한 환경에서만 실행합니다.

요일 제약

days 메서드로 특정 요일에만 작업을 실행하도록 제한할 수 있습니다. 예를 들어 일요일과 수요일에 매시간 명령을 실행하도록 예약합니다.

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

작업을 실행할 요일을 정의할 때 Illuminate\Console\Scheduling\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');

조건 검사 제약

when 메서드로 조건 검사 결과에 따라 작업 실행을 제한할 수 있습니다. 전달한 클로저가 true를 반환하고 다른 제약 조건이 실행을 막지 않으면 작업이 실행됩니다.

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

skip은 when의 반대라고 볼 수 있습니다. skip에 전달한 클로저가 true를 반환하면 예약 작업을 실행하지 않습니다.

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

when 메서드를 여러 개 연결하면 모든 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

일부 시간대에는 일광 절약 시간제가 적용됩니다. 전환 시점에는 예약 작업이 두 번 실행되거나 아예 실행되지 않을 수 있습니다. 따라서 가능하면 시간대를 지정한 스케줄링을 피하는 것을 권장합니다.

작업 중복 실행 방지

기본적으로 이전 작업이 아직 실행 중이어도 다음 예약 작업이 실행됩니다. 이를 방지하려면 withoutOverlapping 메서드를 사용합니다.

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

이 예제의 emails:send Artisan 명령은 이미 실행 중인 경우를 제외하고 매분 실행됩니다. 실행 시간이 크게 달라져 작업이 얼마나 걸릴지 예측하기 어려울 때 withoutOverlapping이 특히 유용합니다.

필요하다면 중복 실행 방지 잠금이 몇 분 후 만료될지 지정할 수 있습니다. 기본 만료 시간은 24시간입니다.

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

withoutOverlapping은 내부적으로 애플리케이션의 캐시를 사용해 잠금을 얻습니다. 필요할 때 schedule:clear-cache Artisan 명령으로 이 캐시 잠금을 지울 수 있습니다. 보통 예기치 못한 서버 문제로 작업이 멈춘 경우에만 필요합니다.

한 서버에서만 작업 실행

WARNING

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

여러 서버에서 스케줄러를 실행하고 있다면 예약 작업을 한 서버에서만 실행하도록 제한할 수 있습니다. 예를 들어 매주 금요일 밤에 보고서를 생성하는 작업이 있고 세 대의 워커 서버에서 스케줄러를 실행한다면, 같은 보고서가 세 번 생성됩니다. 이를 방지해야 합니다.

예약 작업을 정의할 때 onOneServer를 사용하면 한 서버에서만 실행됩니다. 작업을 먼저 확보한 서버가 원자적 잠금을 획득하여 다른 서버가 같은 작업을 동시에 실행하지 못하게 합니다.

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

모든 예약 작업에 onOneServer를 적용하려면 애플리케이션의 AppServiceProvider에 있는 boot 메서드에서 alwaysOnOneServer를 호출할 수 있습니다.

use Illuminate\Support\Facades\Schedule; public function boot(): void { Schedule::alwaysOnOneServer(); }

이름이 없는 예약 클로저는 이 설정에서 제외되며 계속 모든 서버에서 실행됩니다.

useCache 메서드로 단일 서버 작업에 필요한 원자적 잠금을 얻을 때 사용할 스케줄러의 캐시 저장소를 변경할 수 있습니다.

Schedule::useCache('database');

단일 서버 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

runInBackground 메서드는 command와 exec 메서드로 예약한 작업에서만 사용할 수 있습니다.

유지보수 모드

서버에서 진행 중인 유지보수를 방해하지 않도록, 애플리케이션이 유지보수 모드일 때는 예약 작업을 실행하지 않습니다. 유지보수 모드에서도 작업을 강제로 실행하려면 정의할 때 evenInMaintenanceMode를 호출합니다.

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

예약 작업 일시 중지

schedule:pause Artisan 명령으로 배포된 코드를 변경하지 않고도 예약 작업 처리를 일시 중지할 수 있습니다.

php artisan schedule:pause

스케줄러가 일시 중지되면 예약 작업은 기본적으로 실행되지 않습니다. schedule:continue 명령으로 예약 작업 처리를 재개할 수 있습니다.

php artisan schedule:continue

스케줄러가 일시 중지된 상태에서도 실행해야 하는 작업은 evenWhenPaused로 표시합니다.

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

일정 그룹

비슷한 설정을 사용하는 예약 작업을 여러 개 정의할 때 Laravel의 작업 그룹 기능을 사용하면 같은 설정을 반복하지 않아도 됩니다. 코드가 간결해지고 관련 작업의 설정을 일관되게 유지할 수 있습니다.

원하는 작업 설정 메서드를 호출한 다음 group 메서드를 호출하면 예약 작업 그룹이 생성됩니다. 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 스케줄러를 사용할 때는 서버에 매분 schedule:run을 실행하는 cron 항목 하나만 추가하면 됩니다. 서버에 cron 항목을 추가하는 방법을 모른다면 예약 작업 실행을 관리해 주는 Laravel Cloud 같은 관리형 플랫폼을 고려할 수 있습니다.

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

1분 미만 주기의 예약 작업

대부분의 운영체제에서 cron은 최대 분당 한 번만 실행할 수 있습니다. 하지만 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:run을 중단하려면 배포 스크립트에 schedule:interrupt 명령을 추가할 수 있습니다. 애플리케이션 배포가 완료된 후에 실행해야 합니다.

php artisan schedule:interrupt

로컬에서 스케줄러 실행

로컬 개발 환경에는 일반적으로 스케줄러의 cron 항목을 추가하지 않습니다. 대신 schedule:work Artisan 명령을 사용할 수 있습니다. 이 명령은 포그라운드에서 실행되며 종료할 때까지 매분 스케줄러를 호출합니다. 1분 미만 주기의 작업이 있으면 각 분 내에서도 스케줄러가 계속 실행되며 해당 작업들을 처리합니다.

php artisan schedule:work

작업 출력

Laravel 스케줄러에는 예약 작업의 출력을 처리하는 편리한 메서드가 여러 개 있습니다. 먼저 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');

예약된 Artisan 명령이나 시스템 명령이 0이 아닌 종료 코드로 종료했을 때만 출력을 이메일로 보내려면 emailOutputOnFailure를 사용합니다.

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

WARNING

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

작업 훅

before와 after 메서드로 예약 작업을 실행하기 전과 후에 실행할 코드를 지정할 수 있습니다.

use Illuminate\Support\Facades\Schedule; Schedule::command('emails:send') ->daily() ->before(function () { // The task is about to execute... }) ->after(function () { // The task has executed... });

onSuccess와 onFailure로 예약 작업이 성공하거나 실패했을 때 실행할 코드를 지정할 수 있습니다. 실패란 예약된 Artisan 명령이나 시스템 명령이 0이 아닌 종료 코드로 종료한 경우를 뜻합니다.

Schedule::command('emails:send') ->daily() ->onSuccess(function () { // The task succeeded... }) ->onFailure(function () { // The task failed... });

명령의 출력이 있다면 after, onSuccess, onFailure 훅에서 접근할 수 있습니다. 훅 클로저의 $output 인수에 Illuminate\Support\Stringable 타입을 선언하세요.

use Illuminate\Support\Stringable; Schedule::command('emails:send') ->daily() ->onSuccess(function (Stringable $output) { // The task succeeded... }) ->onFailure(function (Stringable $output) { // The task failed... });

URL 호출

pingBefore와 thenPing을 사용하면 스케줄러가 작업 실행 전이나 후에 지정한 URL을 자동으로 호출합니다. Envoyer 같은 외부 서비스에 예약 작업의 시작이나 완료를 알릴 때 유용합니다.

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

pingOnSuccess와 pingOnFailure로 작업이 성공하거나 실패했을 때만 지정한 URL을 호출할 수 있습니다. 실패는 예약된 Artisan 명령이나 시스템 명령이 0이 아닌 종료 코드로 종료한 경우입니다.

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

pingBeforeIf, thenPingIf, pingOnSuccessIf, pingOnFailureIf 메서드를 사용하면 지정한 조건이 true일 때만 URL을 호출할 수 있습니다.

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년 10월 9일