스케줄링
번역일: 2026년 6월 20일
스케줄링
소개
예전에는 서버에서 주기적으로 실행해야 하는 작업마다 cron 설정을 직접 추가해야 했습니다. 그런데 이 방식에는 문제가 있습니다. 스케줄 설정이 소스 코드 저장소 밖에 존재하기 때문에, 현재 등록된 cron 항목을 확인하거나 새로 추가하려면 매번 서버에 SSH로 접속해야 합니다.
Laravel의 커맨드 스케줄러는 이 문제를 깔끔하게 해결합니다. 스케줄 설정을 애플리케이션 코드 안에서 직접 정의할 수 있으며, 서버에는 딱 하나의 cron 항목만 등록하면 됩니다. 스케줄은 app/Console/Kernel.php 파일의 schedule 메서드에서 정의합니다.
스케줄 정의하기
모든 스케줄 작업은 App\Console\Kernel 클래스의 schedule 메서드 안에 정의합니다. 아래 예시를 살펴보겠습니다. 매일 자정에 클로저를 실행하여 데이터베이스의 특정 테이블을 비웁니다.
<?php
namespace App\Console;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
use Illuminate\Support\Facades\DB;
class Kernel extends ConsoleKernel
{
/**
* 애플리케이션의 커맨드 스케줄을 정의합니다.
*/
protected function schedule(Schedule $schedule): void
{
$schedule->call(function () {
DB::table('recent_users')->delete();
})->daily();
}
}클로저 외에도 __invoke 메서드를 가진 인보커블 객체를 사용할 수 있습니다.
$schedule->call(new DeleteRecentUsers)->daily();등록된 스케줄 작업 목록과 다음 실행 예정 시각을 확인하려면 schedule:list Artisan 커맨드를 사용하세요.
php artisan schedule:listArtisan 커맨드 스케줄링
클로저 외에도 Artisan 커맨드와 시스템 커맨드를 스케줄링할 수 있습니다. command 메서드를 사용하면 커맨드 이름이나 클래스로 스케줄을 등록할 수 있습니다.
클래스 이름으로 등록할 때는 추가 커맨드라인 인수를 배열로 함께 전달할 수 있습니다.
use App\Console\Commands\SendEmailsCommand;
$schedule->command('emails:send Taylor --force')->daily();
$schedule->command(SendEmailsCommand::class, ['Taylor', '--force'])->daily();큐 Job 스케줄링
job 메서드를 사용하면 큐 Job을 스케줄링할 수 있습니다. 클로저로 call을 작성할 필요 없이 간편하게 Job을 등록할 수 있습니다.
use App\Jobs\Heartbeat;
$schedule->job(new Heartbeat)->everyFiveMinutes();job 메서드의 두 번째, 세 번째 인수로 큐 이름과 큐 연결 이름을 지정할 수도 있습니다.
use App\Jobs\Heartbeat;
// "sqs" 연결의 "heartbeats" 큐로 Job을 디스패치합니다.
$schedule->job(new Heartbeat, 'heartbeats', 'sqs')->everyFiveMinutes();셸 커맨드 스케줄링
exec 메서드를 사용하면 운영체제의 셸 커맨드를 직접 실행할 수 있습니다.
$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시 사이에만 매시 실행하도록 설정할 수 있습니다.
// 매주 월요일 13:00에 한 번 실행
$schedule->call(function () {
// ...
})->weekly()->mondays()->at('13:00');
// 평일 8:00~17:00 사이에 매시 실행
$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이 일요일, 6이 토요일입니다.
$schedule->command('emails:send')
->hourly()
->days([0, 3]); // 일요일과 수요일Schedule 클래스에서 제공하는 상수를 사용하면 더 명확하게 표현할 수 있습니다.
use Illuminate\Console\Scheduling\Schedule;
$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의 반대입니다. 클로저가 true를 반환하면 해당 작업은 건너뜁니다.
$schedule->command('emails:send')->daily()->skip(function () {
return true;
});when을 여러 번 체이닝하면 모든 조건이 true일 때만 실행됩니다.
환경별 실행 제한
environments 메서드를 사용하면 APP_ENV 환경 변수에 따라 특정 환경에서만 작업을 실행할 수 있습니다.
$schedule->command('emails:send')
->daily()
->environments(['staging', 'production']);타임존
timezone 메서드를 사용하면 작업 시각을 특정 타임존 기준으로 해석합니다.
$schedule->command('report:generate')
->timezone('America/New_York')
->at('2:00');모든 스케줄 작업에 동일한 타임존을 반복 지정하고 싶다면 App\Console\Kernel 클래스에 scheduleTimezone 메서드를 정의하세요.
use DateTimeZone;
/**
* 스케줄 이벤트에 기본으로 적용할 타임존을 반환합니다.
*/
protected function scheduleTimezone(): DateTimeZone|string|null
{
return 'Asia/Seoul';
}WARNING
일부 타임존은 서머타임(DST)을 적용합니다. 서머타임 전환 시 작업이 두 번 실행되거나 아예 실행되지 않을 수 있습니다. 가능하면 타임존 지정 방식의 스케줄링은 피하는 것을 권장합니다.
작업 중복 실행 방지
기본적으로 이전 실행이 아직 끝나지 않았더라도 스케줄 시간이 되면 작업이 새로 시작됩니다. 이를 방지하려면 withoutOverlapping 메서드를 사용하세요.
$schedule->command('emails:send')->withoutOverlapping();이 예시에서 emails:send 커맨드가 이미 실행 중이 아니라면 매분 실행됩니다. withoutOverlapping은 실행 시간이 불규칙하게 달라지는 작업에 특히 유용합니다.
락(lock)의 만료 시간을 분 단위로 직접 지정할 수도 있습니다. 기본값은 24시간입니다.
$schedule->command('emails:send')->withoutOverlapping(10);내부적으로 withoutOverlapping은 애플리케이션의 캐시를 이용해 락을 획득합니다. 서버 장애 등으로 작업이 멈춰 락이 해제되지 않는 경우, schedule:clear-cache Artisan 커맨드로 캐시 락을 직접 제거할 수 있습니다.
php artisan schedule:clear-cache단일 서버에서 작업 실행
WARNING
이 기능을 사용하려면 애플리케이션의 기본 캐시 드라이버가 database, memcached, dynamodb, redis 중 하나여야 합니다. 또한 모든 서버가 동일한 중앙 캐시 서버를 공유해야 합니다.
스케줄러가 여러 서버에서 동시에 실행되는 환경(예: 다중 워커 서버 구성)에서는 동일한 작업이 서버 수만큼 중복 실행되는 문제가 생길 수 있습니다. onOneServer 메서드를 사용하면 가장 먼저 락을 획득한 서버에서만 해당 작업을 실행하고, 나머지 서버에서는 건너뜁니다.
$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();백그라운드 작업
기본적으로 같은 시각에 예약된 작업들은 schedule 메서드에 정의된 순서대로 순차적으로 실행됩니다. 실행 시간이 긴 작업이 있으면 이후 작업들이 늦게 시작될 수 있습니다. 작업들을 동시에 실행하려면 runInBackground 메서드를 사용하세요.
$schedule->command('analytics:report')
->daily()
->runInBackground();WARNING
runInBackground 메서드는 command와 exec 메서드로 등록한 작업에만 사용할 수 있습니다.
점검 모드
애플리케이션이 점검 모드(maintenance mode)로 전환되면 스케줄 작업은 기본적으로 실행되지 않습니다. 점검 중인 서버 작업에 스케줄이 간섭하는 것을 방지하기 위해서입니다. 점검 모드에서도 반드시 실행해야 하는 작업이 있다면 evenInMaintenanceMode 메서드를 사용하세요.
$schedule->command('emails:send')->evenInMaintenanceMode();스케줄러 실행하기
스케줄을 정의했다면, 이제 서버에서 실제로 실행하는 방법을 알아보겠습니다. schedule:run Artisan 커맨드는 등록된 모든 스케줄 작업을 확인하고, 서버의 현재 시각을 기준으로 실행 여부를 판단합니다.
서버에 아래 cron 항목 하나만 등록하면 됩니다. 이 cron은 매분 schedule:run을 실행합니다. cron 설정이 익숙하지 않다면 Laravel Forge와 같은 서비스를 이용하면 자동으로 관리할 수 있습니다.
* * * * * cd /path-to-your-project && php artisan schedule:run >> /dev/null 2>&11분 미만 단위 작업
대부분의 운영체제에서 cron은 최소 1분 단위로만 실행할 수 있습니다. 하지만 Laravel 스케줄러는 초(second) 단위까지 작업을 실행할 수 있습니다.
$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이 1분 내내 실행됩니다. 따라서 애플리케이션을 새로 배포했을 때, 이미 실행 중인 schedule:run 프로세스가 이전 코드를 계속 사용하는 문제가 생길 수 있습니다. 이를 방지하려면 배포 스크립트 마지막에 schedule:interrupt 커맨드를 실행하여 진행 중인 schedule:run을 중단하세요.
php artisan schedule:interrupt로컬에서 스케줄러 실행하기
로컬 개발 환경에서는 서버에 cron을 등록하는 대신 schedule:work Artisan 커맨드를 사용하세요. 이 커맨드는 포그라운드에서 실행되며, 종료하기 전까지 매분 스케줄러를 호출합니다.
php artisan schedule:work작업 출력
스케줄 작업이 생성한 출력을 처리하는 여러 방법을 제공합니다.
sendOutputTo 메서드를 사용하면 출력 결과를 파일로 저장할 수 있습니다.
$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('admin@example.com');작업이 실패(비정상 종료 코드)했을 때만 이메일을 받고 싶다면 emailOutputOnFailure 메서드를 사용하세요.
$schedule->command('report:generate')
->daily()
->emailOutputOnFailure('admin@example.com');WARNING
emailOutputTo, emailOutputOnFailure, sendOutputTo, appendOutputTo 메서드는 command와 exec 메서드로 등록한 작업에서만 사용할 수 있습니다.
작업 훅
before와 after 메서드를 사용하면 작업 실행 전후에 코드를 실행할 수 있습니다.
$schedule->command('emails:send')
->daily()
->before(function () {
// 작업 실행 전...
})
->after(function () {
// 작업 실행 후...
});onSuccess와 onFailure 메서드로 성공 또는 실패 시에만 실행할 코드를 지정할 수 있습니다. 실패란 Artisan 또는 시스템 커맨드가 0이 아닌 종료 코드로 종료된 경우를 의미합니다.
$schedule->command('emails:send')
->daily()
->onSuccess(function () {
// 작업 성공 시...
})
->onFailure(function () {
// 작업 실패 시...
});커맨드 출력이 있다면 after, onSuccess, onFailure 훅의 클로저에서 Illuminate\Support\Stringable 타입 힌트를 통해 출력 내용에 접근할 수 있습니다.
use Illuminate\Support\Stringable;
$schedule->command('emails:send')
->daily()
->onSuccess(function (Stringable $output) {
// 작업 성공 시 출력 내용 활용...
})
->onFailure(function (Stringable $output) {
// 작업 실패 시 출력 내용 활용...
});URL 핑(Ping)
pingBefore와 thenPing 메서드를 사용하면 작업 실행 전후에 지정한 URL로 HTTP 요청을 보낼 수 있습니다. Envoyer와 같은 외부 서비스에 작업 시작/완료를 알릴 때 유용합니다.
$schedule->command('emails:send')
->daily()
->pingBefore($url)
->thenPing($url);조건에 따라 핑을 보내려면 pingBeforeIf와 thenPingIf를 사용하세요.
$schedule->command('emails:send')
->daily()
->