Cron 표현식 해부: 다섯 필드, 범위, 스텝, 요일 OR 함정
다섯 필드와 유효한 값 범위
표준 cron 표현식은 왼쪽에서 오른쪽으로 읽는 공백으로 구분된 정확히 다섯 개의 필드로 구성됩니다: 분 (0–59), 시 (0–23), 날짜 (1–31), 월 (1–12), 요일 (0–7). 분은 항상 가장 왼쪽 필드입니다——cron 행을 오른쪽에서 왼쪽으로 읽는 것은 의미가 완전히 반전되는 일반적인 실수입니다. 요일 필드는 0과 7 모두 일요일을 나타내는 것으로 받아들입니다. 이는 Unix 전통에서 이어진 의도적인 중복성입니다.
Vixie cron 관례를 따르는 구현에서는 월 필드가 jan부터 dec까지의 세 글자 영어 약어도 허용하고, 요일 필드는 sun부터 sat를 허용합니다. 환경 간 이식성을 위해서는 숫자 값을 사용하는 것이 더 안전합니다. Quartz Scheduler(Java 애플리케이션에서 사용)는 맨 앞에 초 필드를 추가하고, AWS EventBridge와 같은 클라우드 스케줄러는 선택적인 일곱 번째 연도 필드를 추가합니다——이러한 확장 형식은 POSIX 5필드 표준과 호환되지 않습니다.
와일드카드, 쉼표 목록, 하이픈 범위, 슬래시 스텝
네 가지 특수 문자가 필드 매칭 방식을 제어합니다: 별표 (*)는 해당 필드의 모든 유효한 값에 매칭됩니다——* * * * *는 매 분마다 실행됩니다. 쉼표 (,)는 개별 값 목록을 만듭니다: 월 필드의 1,3,5는 1월, 3월, 5월을 의미합니다. 하이픈 (-)은 포함 범위를 정의합니다: 시 필드의 9-17은 9시부터 17시까지 (양 끝 포함) 매 시간에 매칭됩니다——'업무 시간 전용' 일정에 유용합니다. 이 세 문자는 결합할 수 있습니다: 요일 필드의 1-5,0은 월요일부터 금요일 그리고 일요일에 매칭됩니다.
슬래시 (/)는 스텝 값을 도입합니다. 별표 뒤에 사용하면, 분 필드의 */15는 집합 {0, 15, 30, 45}를 생성합니다——필드 최솟값부터 15의 배수마다. 범위 뒤에 사용하면, 0-30/5는 {0, 5, 10, 15, 20, 25, 30}을 생성합니다. 스텝은 항상 범위의 왼쪽 경계(또는 *의 경우 0)에서 시작하며, 임의의 오프셋에서 시작하지 않습니다. 이것이 분 필드의 */5가 cron 데몬이 언제 시작되었는지에 관계없이 0, 5, 10……에서 실행되는 이유입니다.
날짜와 요일의 OR 함정
cron에서 가장 놀라운 동작은 날짜 필드와 요일 필드 모두 와일드카드가 아닌 값으로 설정될 때 발생합니다. 직관적으로, 0 2 15 * 5는 '매월 15일이면서 금요일인 날 오전 2시에 실행'——AND 조건——을 의미하는 것처럼 보입니다. 실제로는, Vixie cron과 dcron을 포함한 대부분의 cron 구현은 이를 OR 조건으로 처리합니다: 작업은 매월 15일 오전 2시에 실행되고, 또한 날짜에 관계없이 매주 금요일 오전 2시에도 실행됩니다. cron(5) 매뉴얼 페이지는 이를 명시적으로 문서화합니다: '만약 DOM과 DOW가 모두 지정되면, 어느 필드가 현재 시간과 일치할 때 명령이 실행된다.'
이 동작은 복합 날짜 조건을 표현하려는 개발자를 당황하게 합니다. '매월 세 번째 금요일'이 정말 필요하다면, cron은 직접 표현할 수 없습니다. 관용적인 해결책은 매주 금요일에 실행하도록 예약하고 (0 2 * * 5) 명령 시작 부분에 셸 테스트를 추가하는 것입니다: [ $(date +\%d) -ge 15 ] && [ $(date +\%d) -le 21 ] && /경로/to/script. 또는 캘린더 반복 규칙(RFC 5545 RRULE)을 기본으로 지원하는 상위 스케줄러, 예를 들어 systemd 타이머의 OnCalendar= 지시문을 사용하십시오.
미리 정의된 단축키: @reboot, @hourly, @daily, @weekly, @monthly
Vixie cron은 일반적인 일정을 위한 5필드 구문을 대체하는 명명된 단축키 세트를 도입했습니다. @reboot는 cron 데몬 자체가 시작된 직후 한 번 작업을 실행합니다——경량 시작 작업에 유용하지만, 프로덕션 서비스에는 적절한 init 시스템(systemd 유닛 등)이 바람직합니다. @hourly는 0 * * * *와 동일합니다. @daily (별칭 @midnight)는 0 0 * * *와 동일하고, @weekly는 0 0 * * 0 (일요일 자정), @monthly는 0 0 1 * * (매월 1일 자정), @yearly (별칭 @annually)는 0 0 1 1 * (1월 1일 자정)과 동일합니다.
이러한 단축키는 crontab 파일의 가독성을 향상시키지만, cron 데몬이 @ 구문을 인식하는 것에 의존합니다——POSIX는 이를 정의하지 않으므로 일부 최소한의 또는 내장된 cron 구현에서는 사용할 수 없습니다. 클라우드 스케줄러(AWS EventBridge, Google Cloud Scheduler, GitHub Actions)를 위한 cron 표현식을 작성할 때는 대부분의 클라우드 플랫폼이 @ 단축키를 구현하지 않고 명시적인 5필드 형식을 요구하므로 공급자 문서를 확인하십시오.
스텝 값과 '지금부터 N마다가 아니다' 함정
스텝 구문에 대한 자주 있는 오해는 */N을 '이 작업이 생성된 때부터 N 단위마다'로 처리하는 것입니다. 그렇지 않습니다. 스텝은 절대적인 시간 값에 대한 모듈로 필터입니다. 분 필드의 */5는 집합 {x : x mod 5 = 0, 0 ≤ x ≤ 59} = {0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55}의 약어입니다——시간당 12개의 고정된 시계 위치. cron 데몬은 이 위치들을 현재 시계 분과 비교합니다. 작업이 언제 등록되었는지 기억하지 않습니다.
이는 0 */6 * * *에서 미묘한 문제를 만듭니다. 이 표현식은 시 0, 6, 12, 18——자정, 오전 6시, 정오, 오후 6시——에 하루 정확히 4번 실행됩니다. 오후 3시에 이 작업을 추가하고 '6시간 후인 오후 9시에 첫 실행'을 기대한다면 놀랄 것입니다: 다음 실행은 오후 6시(시 18)입니다. 오후 9시에 실행이 필요하다면 명시적인 목록 0 9,15,21,3 * * *을 사용하거나 0 3/6 * * *으로 시작 시간을 변경하십시오——하지만 시작/스텝 표기법은 Vixie cron 확장이므로 모든 곳에서 사용할 수 있는 것은 아닙니다. 최대 이식성을 위해 항상 명시적인 쉼표로 구분된 목록을 사용하십시오.
시간대: cron 일정의 보이지 않는 버그
전통적인 cron 데몬은 실행되는 머신의 시스템 시간대에서 모든 작업을 실행합니다. UTC로 구성된 서버——대부분의 Docker 컨테이너 기본 이미지와 많은 클라우드 VM의 기본값——에서 0 8 * * 1-5는 현지 시간 오전 8시를 의미하지 않습니다. UTC 오전 8시를 의미합니다. 팀이 UTC+9(일본 표준시)에 있다면 그 작업은 현지 시간 오후 5시에 실행됩니다. AWS CloudWatch Events(현재 EventBridge)의 cron 표현식은 UTC 전용으로 문서화되어 있습니다. GitHub Actions의 on: schedule:도 UTC입니다.
일광 절약 시간(DST)은 두 가지 추가 장애 모드를 도입합니다. 시계가 앞으로 이동할 때(예: 01:59에서 03:00으로), 건너뛴 시간에 예약된 cron 작업은 그날 단순히 실행되지 않습니다. 시계가 뒤로 이동할 때(02:59에서 02:00으로), 같은 분이 두 번 나타납니다. 작업이 한 번 실행되는지 두 번 실행되는지는 데몬 구현에 따라 다릅니다. 가장 안전한 프로덕션 전략은 cron 서버를 영구적으로 UTC로 실행하고 애플리케이션 계층에서 현지 시간으로 변환하거나, 명시적인 TimeZone 매개변수를 허용하는 스케줄러를 사용하는 것입니다. 예: TimeZone=을 가진 systemd 타이머, Kubernetes 1.25에서 추가된 .spec.timeZone을 가진 Kubernetes CronJob, 또는 IANA 시간대 이름을 허용하는 AWS EventBridge Scheduler.