스케줄링
번역일: 2026년 6월 25일
스케줄링
소개
과거에는 서버에서 주기적으로 실행할 작업마다 별도의 cron 항목을 작성해야 했습니다. 하지만 이 방식은 금방 불편해집니다. 스케줄 설정이 소스 코드에 포함되지 않고, 항목을 확인하거나 추가하려면 매번 서버에 SSH로 접속해야 하기 때문입니다.
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 메서드를 가진 인보커블 객체를 스케줄링할 수 있습니다.
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:listArtisan 커맨드 스케줄링
클로저 외에도 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을 간편하게 스케줄링할 수 있습니다. 클로저로 직접 디스패치하는 call 방식보다 훨씬 간결합니다.
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에 실행 |
->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(); | 매년 1월 1일 00:00에 실행 |
->yearlyOn(6, 1, '17:00'); | 매년 6월 1일 17:00에 실행 |
->timezone('America/New_York'); | 태스크의 타임존 지정 |
이 메서드들은 추가 조건과 조합하여 더 세밀하게 실행 시점을 제어할 수 있습니다. 예를 들어, 매주 월요일 오후 1시에만 실행하거나 평일 업무 시간대에만 실행하도록 설정할 수 있습니다.
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');조건부 실행 제약
when 메서드에 클로저를 전달하면, 해당 클로저가 true를 반환할 때만 태스크가 실행됩니다. 다른 제약 조건이 없는 한, 클로저가 true이면 정상 실행됩니다.
Schedule::command('emails:send')->daily()->when(function () {
return true;
});skip은 when의 반대입니다. 클로저가 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('Asia/Seoul')
->at('2:00')모든 스케줄 태스크에 동일한 타임존을 적용하려면, app 설정 파일에 schedule_timezone 옵션을 추가하세요.
'timezone' => 'UTC',
'schedule_timezone' => 'Asia/Seoul',WARNING
일부 타임존은 서머타임(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();스케줄러가 단일 서버 태스크에 사용하는 캐시 스토어를 변경하려면 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();스케줄 그룹
비슷한 설정을 공유하는 여러 태스크를 정의할 때 동일한 설정을 반복 작성하는 것은 번거롭습니다. 스케줄 그룹 기능을 사용하면 공통 설정을 한 번만 지정하고 여러 태스크에 일괄 적용할 수 있습니다.
group 메서드 앞에 원하는 설정 메서드들을 체이닝하고, group에는 태스크를 정의하는 클로저를 전달합니다.
use Illuminate\Support\Facades\Schedule;
Schedule::daily()
->onOneServer()
->timezone('Asia/Seoul')
->group(function () {
Schedule::command('emails:send --force');
Schedule::command('emails:prune');
});스케줄러 실행하기
스케줄을 정의했다면, 실제로 서버에서 어떻게 실행하는지 알아보겠습니다. schedule:run Artisan 커맨드는 등록된 모든 스케줄 태스크를 점검하고, 현재 서버 시각 기준으로 실행이 필요한 태스크를 수행합니다.
Laravel 스케줄러를 사용할 때는 서버 cron에 아래 항목 하나만 등록하면 됩니다. 이 cron은 매 분마다 schedule:run을 호출합니다. Laravel Cloud와 같은 관리형 플랫폼을 사용한다면 cron 설정을 직접 관리해 주므로 더 편리합니다.
* * * * * cd /path-to-your-project && php artisan schedule:run >> /dev/null 2>&11분 미만 주기 태스크
일반 운영체제의 cron은 최소 1분 간격으로만 실행됩니다. 하지만 Laravel 스케줄러는 1초 단위까지 더 짧은 주기로도 태스크를 실행할 수 있습니다.
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 커맨드를 추가하면, 배포 완료 후 실행 중인 schedule:run을 중단시킬 수 있습니다.
php artisan schedule:interrupt로컬에서 스케줄러 실행하기
로컬 개발 환경에서는 서버 cron을 따로 설정하는 대신, schedule:work Artisan 커맨드를 사용하세요. 이 커맨드는 포그라운드에서 실행되며 Ctrl+C로 종료할 때까지 매 분마다 스케줄러를 호출합니다. 1분 미만 주기 태스크가 있는 경우에도 해당 분 내내 태스크를 처리합니다.
php artisan schedule:work태스크 출력
Laravel 스케줄러는 태스크 실행 결과 출력을 다루는 편리한 메서드를 제공합니다.
sendOutputTo 메서드를 사용하면 출력을 파일로 저장할 수 있습니다.
use Illuminate\Support\Facades\Schedule;
Schedule::command('emails:send')
->daily()
->sendOutputTo($filePath);기존 파일에 출력을 이어서 추가하려면 appendOutputTo 메서드를 사용하세요.
Schedule::command('