cron의 날짜와 요일은 OR: 둘째 월요일·월말·마지막 금요일·격주를 올바르게 쓰는 법
"매월 첫째 월요일 9시"라는 생각으로 "0 9 1-7 * 1"이라고 썼더니 매주 월요일에 더해 매월 1~7일 매일에도 실행됐다. cron에서 아주 흔한 이 실수는 날짜와 요일을 둘 다 지정하면 "둘 중 하나"에 해당하는 날에 실행된다는 cron 특유의 규칙 때문입니다. 이 글에서는 날짜와 요일이 OR이 되는 원리와 예외, crontab에서 n번째 요일·월말·마지막 금요일·격주를 올바르게 실행하는 방법, AWS EventBridge와 Spring에서 쓸 수 있는 L·#·W 표기까지 실제 실행 날짜 예시와 함께 설명합니다.
날짜와 요일을 둘 다 쓰면 "둘 중 하나"인 날에 실행된다
cron 식의 5개 필드 중 분·시·월은 "모든 조건을 만족하는 시각"에 실행한다는 직관적인 방식으로 읽힙니다. 그런데 날짜(3번째)와 요일(5번째)만은 특별해서, 둘 다 * 이외의 값을 쓰면 "날짜 조건이나 요일 조건 중 하나를 만족하는 날"에 실행됩니다. 이것은 Linux cron(Vixie cron과 그 계보인 cronie 등)의 오래된 사양이며, GitHub Actions와 Kubernetes CronJob도 같은 방식으로 읽습니다.
예를 들어 "매월 첫째 월요일 9시"라는 생각으로 쓴 "0 9 1-7 * 1"은 "매월 1~7일" 또는 "매주 월요일"의 9:00에 실행됩니다. 2026년 10월 12일(월)부터 세면 실제 실행일은 다음과 같습니다.
| 실행일 | 해당한 조건 |
|---|---|
| 10월 12일(월) | 요일이 월요일 |
| 10월 19일(월) | 요일이 월요일 |
| 10월 26일(월) | 요일이 월요일 |
| 11월 1일(일) | 날짜가 1~7일 |
| 11월 2일(월) | 날짜가 1~7일, 요일도 월요일 |
| 11월 3일(화) | 날짜가 1~7일 |
첫째 월요일만 의도했는데 매주 월요일과 매월 첫 7일 동안 실행되어 한 달에 10번 안팎 실행됩니다. 정기 회의 알림이나 월간 보고서 발송을 이렇게 설정하면 거의 매주 알림이 가는 사고가 납니다.
예외: 어느 한쪽이 *로 시작하면 "둘 다"가 된다
더 헷갈리게도 Linux cron에는 "날짜나 요일 값이 *로 시작하면 OR이 아니라 AND(둘 다 만족하는 날)가 된다"는 예외가 있습니다. "*/2"처럼 *로 시작하는 간격 지정이 여기에 해당합니다.
| 식 | 해석 | 실행되는 날 |
|---|---|---|
| 0 10 1-31/2 * 2 | OR(둘 중 하나) | 홀수일 매일 + 매주 화요일 |
| 0 10 */2 * 2 | AND(둘 다) | 홀수일인 화요일만 |
"1-31/2"와 "*/2"는 둘 다 1·3·5…31일을 나타내는데, 표기 하나 차이로 실행일이 완전히 달라집니다. 2026년 10월 이후 "0 10 */2 * 2"의 실행일을 나열하면 10월 13일·10월 27일·11월 3일·11월 17일·12월 1일…로 홀수일의 화요일뿐입니다. 이 예외는 구현의 세부 동작이라 다른 스케줄러로 옮기면 해석이 바뀔 수도 있습니다. 의도적으로 쓰기보다는 "이 표기는 피한다"고 기억해 두는 편이 안전합니다.
n번째 ○요일을 crontab으로 실행하는 방법
crontab에는 "둘째 화요일"을 직접 나타내는 표기가 없습니다. 그래서 n번째 요일은 반드시 "(n−1)×7+1일 ~ n×7일"의 7일 중 어딘가에 있다는 성질을 이용합니다. 첫째는 1~7일, 둘째는 8~14일, 셋째는 15~21일, 넷째는 22~28일입니다.
- 1
날짜 필드로 7일을 좁힌다
둘째 화요일 10:00이라면 날짜 필드를 8-14, 요일 필드를 *로 해서 "0 10 8-14 * *"로 씁니다. 요일을 2로 쓰면 OR이 되므로 여기서는 쓰지 않습니다.
- 2
명령 앞부분에서 요일을 확인한다
date +%u는 월요일을 1, 일요일을 7로 하는 요일 숫자를 돌려줍니다. crontab에서는 %를 이스케이프해 "[ "$(date +\%u)" = 2 ] && /path/to/command"로 쓰면 화요일일 때만 뒤의 명령이 실행됩니다.
- 3
실행일로 확인한다
cron 입장에서는 8~14일 매일 실행되므로, 명령이 정말 둘째 화요일에만 실행되는지 스크립트 로그나 수동 테스트로 확인해 둡니다.
AWS EventBridge와 Spring의 @Scheduled라면 #를 써서 직접 쓸 수 있습니다. EventBridge는 요일 숫자가 1(일요일)부터 시작하므로 둘째 화요일 10:00은 "cron(0 10 ? * 3#2 *)", Spring이라면 "0 0 10 ? * TUE#2"입니다. EventBridge 표기로 2026년 10월 이후를 나열하면 10월 13일·11월 10일·12월 8일·1월 12일로 매월 둘째 화요일뿐입니다.
매월 말일·마지막 금요일 작성법(31일 지정의 함정)
"매월 말일에 마감 처리"를 "0 23 31 * *"로 쓰는 것은 전형적인 실수입니다. 31일이 없는 2·4·6·9·11월에는 한 번도 실행되지 않습니다. 마찬가지로 30일을 지정하면 2월에 실행되지 않고, "0 9 30 2 *"(2월 30일)처럼 존재하지 않는 날짜를 지정하면 영원히 실행되지 않는 식이 됩니다.
| 하고 싶은 것 | crontab | EventBridge | Spring |
|---|---|---|---|
| 매월 말일 23:00 | 0 23 28-31 * * + 말일 확인 | cron(0 23 L * ? *) | 0 0 23 L * * |
| 월말 3일 전 | 0 9 25-28 * * + 날짜 확인 | cron(0 9 L-3 * ? *) | 0 0 9 L-3 * * |
| 마지막 금요일 18:00 | 0 18 22-31 * * + 요일과 주 확인 | cron(0 18 ? * 6L *) | 0 0 18 ? * 5L |
| 마지막 평일 | 0 18 26-31 * * + 평일 확인 | cron(0 18 LW * ? *) | 0 0 18 LW * * |
| 15일에 가장 가까운 평일 | 쓸 수 없음(명령 쪽에서 판정) | cron(0 9 15W * ? *) | 0 0 9 15W * * |
crontab에서 말일을 판정하려면 "내일이 1일이면 오늘이 말일"이라는 생각을 이용합니다. GNU date(일반적인 Linux)라면 "0 23 28-31 * * [ "$(date -d tomorrow +\%d)" = "01" ] && /path/to/command"로 쓰면 28~31일 중 말일에만 명령이 실행됩니다. 마지막 금요일이라면 날짜를 22-31로 좁힌 뒤 "오늘이 금요일"이고 "7일 뒤가 다음 달"인지 확인합니다(예: [ "$(date +\%u)" = 5 ] && [ "$(date -d '+7 days' +\%m)" != "$(date +\%m)" ] && 명령).
EventBridge의 L은 "그달의 말일", 6L은 "마지막 금요일(6이 금요일)", 15W는 "15일에 가장 가까운 평일"입니다. 예를 들어 15W를 2026년 11월로 계산하면 15일이 일요일이므로 다음 날인 16일(월)에 실행됩니다. W는 달을 넘지 않으므로 1W가 토요일이면 3일(월)이 됩니다.
격주·2주에 한 번은 cron만으로는 쓸 수 없다
"격주 월요일"은 cron의 5개 필드로 나타낼 수 없습니다. 날짜 필드의 "*/14"는 "1·15·29일"을 가리킬 뿐이고 달이 바뀌면 다시 1일부터 세므로 2주 간격이 되지 않습니다. 주 번호 필드도 없으므로 명령 쪽에서 판정하는 것이 정석입니다.
- 매주 월요일에 실행하고 ISO 주 번호가 짝수인 주에만 실행한다: 0 9 * * 1 [ $(( $(date +\%-V) \% 2 )) -eq 0 ] && 명령(%-V는 08 같은 앞자리 0을 없애는 지정. 0이 붙은 08·09는 셸 계산에서 8진수로 읽혀 오류가 난다)
- 주 번호는 해가 바뀌면 52나 53에서 1로 돌아가므로, 53주가 있는 해의 연말연시에는 2주 연속이 되거나 3주가 비는 경우가 있다
- 엄밀히 14일 간격으로 하고 싶다면 기준일로부터 경과 일수를 계산해 14로 나누어떨어지는 날에만 실행한다(예: 기준일 UNIX 시간과의 차를 86400으로 나눈다)
- 작업 쪽에서 지난번 실행일을 파일에 기록하고 13일 이상 지났으면 실행하는 방법도 확실하다
표기별 차이 정리와 확인 방법
날짜와 요일의 취급은 cron을 쓰는 시스템에 따라 다릅니다. 같은 식이라도 옮긴 곳에서 의미가 바뀔 수 있으므로 어떤 시스템으로 실행할지 먼저 확인해 두세요.
| 시스템 | 날짜와 요일을 둘 다 썼을 때 | L·#·W |
|---|---|---|
| crontab(Linux) | 둘 중 하나(OR). *로 시작하면 둘 다(AND) | 쓸 수 없음 |
| GitHub Actions | crontab과 같음 | 쓸 수 없음 |
| Kubernetes CronJob | crontab과 같음 | 쓸 수 없음 |
| AWS EventBridge | 둘 다는 쓸 수 없음. 한쪽을 ?로 | 쓸 수 있음 |
| Spring @Scheduled | 둘 다(AND) | 쓸 수 있음 |
| systemd timer | 둘 다(AND) | ~로 말일부터 세는 표기가 있음 |
Spring에서는 "0 0 9 1-7 * MON"이라고 쓰면 그대로 "매월 1~7일 중 월요일", 즉 첫째 월요일이 됩니다. crontab 감각으로 쓰면 반대 의미가 되므로 이식할 때 특히 주의해야 합니다. EventBridge 표기는 EventBridge cron 식 작성법에서 자세히 설명합니다.
cron 식 검사기에서는 날짜와 요일을 둘 다 지정하면 "둘 중 하나에 해당하는 날에 실행됩니다"라고 경고하고, *로 시작해 AND가 되는 경우도 알려 줍니다. 31일만 지정해 실행되지 않는 달이 있거나, 존재하지 않는 날짜라 한 번도 실행되지 않는 실수도 찾아내며, 다음 실행일을 나열해 확인할 수 있습니다. crontab의 기본 작성법은 crontab 작성법과 빠른 참조표, GitHub Actions를 일본 시간으로 실행하고 싶다면 GitHub Actions cron을 일본 시간으로 실행하는 방법도 함께 보세요.
자주 묻는 질문
- cron으로 매월 둘째 월요일에 실행하려면 어떻게 쓰나요?
- crontab에서는 날짜 필드를 8-14, 요일 필드를 *로 해서 "0 9 8-14 * *"로 하고, 명령 앞부분에서 월요일인지 확인합니다([ "$(date +\%u)" = 1 ] && 명령). 요일 필드에 1을 쓰면 OR이 되어 매주 월요일에도 실행됩니다. EventBridge라면 cron(0 9 ? * 2#2 *), Spring이라면 0 0 9 ? * MON#2로 직접 쓸 수 있습니다.
- cron으로 매월 말일에 실행하는 방법은?
- crontab에는 말일을 나타내는 표기가 없으므로 날짜 필드를 28-31로 해서 매일 실행하고, "내일이 1일이면 실행"하는 판정을 명령에 붙입니다([ "$(date -d tomorrow +\%d)" = "01" ] && 명령). EventBridge나 Spring이라면 날짜 필드에 L이라고 쓰기만 하면 말일이 됩니다. 31이라고 쓰면 31일이 없는 달에는 실행되지 않습니다.
- cron의 날짜와 요일은 왜 AND가 아니라 OR인가요?
- 초기 Unix cron부터 이어진 사양으로, "매월 1일과 15일, 그리고 매주 금요일에도 실행" 같은 지정을 한 줄로 쓸 수 있도록 날짜와 요일을 둘 다 쓰면 OR이 되도록 정해졌습니다. 호환성 때문에 Linux cron, GitHub Actions, Kubernetes에서도 같은 해석이 이어지고 있습니다. Spring과 systemd는 AND입니다.
- cron으로 격주(2주에 한 번) 실행할 수 있나요?
- cron 식만으로는 쓸 수 없습니다. 매주 실행한 뒤 명령 쪽에서 주 번호의 짝수·홀수를 판정하거나(date +\%V를 2로 나눈 나머지를 본다), 지난번 실행일을 기록해 14일이 지났는지 확인하는 방법을 씁니다. 날짜 필드의 */14는 1·15·29일을 나타낼 뿐 2주 간격이 되지 않습니다.
이 글에서 소개한 도구
함께 읽으면 좋은 글
GitHub Actions cron을 일본 시간으로 실행하기: UTC 변환표와 요일 밀림·지연 대책
GitHub Actions의 schedule에 "0 9 * * 1-5"라고 썼는데 워크플로가 일본 시간 오후 6시에 실행됐다면, 오타가 아니라 GitHub Actions의 cron이 항상 UTC(협정 세계시)로 해석되기 때문입니다. 이 글에서는 일본 시간(JST)을 UTC cron 식으로 바꾸는 계산과 변환표, 오전 0~8시를 지정할 때 생기는 "요일·날짜가 전날로 밀리는" 문제, 한 줄로 쓸 수 없을 때 두 줄로 나누는 방법, schedule이 늦거나 실행되지 않을 때의 원인과 대책을 정리합니다. 한국 표준시(KST)도 UTC+9라서 같은 표를 그대로 쓸 수 있습니다.
글 읽기 →crontab 작성법과 빠른 참조표: 5개 필드와 기호의 의미, 자주 쓰는 예, 등록 명령
"매일 3시에 백업", "5분마다 모니터링 스크립트" 같은 정기 실행은 Linux에서는 crontab에 한 줄만 쓰면 설정할 수 있습니다. 하지만 "분 시 일 월 요일" 5개 필드와 * , - / 기호의 조합은 익숙해지기 전까지 잘못 읽기 쉬워서, 1시간에 60번 실행되거나 7분마다 돌리려던 것이 4분 간격이 되는 사고도 자주 일어납니다. 이 글에서는 crontab 작성법의 기본과 기호의 의미, 그대로 쓸 수 있는 빠른 참조표, crontab -e·-l·-r 사용법, 명령 부분을 쓸 때의 주의점을 예시 중심으로 설명합니다.
글 읽기 →EventBridge cron 식 작성법: ?의 의미·요일 숫자·일본 시간 지정과 crontab에서 바꿔 쓰기
AWS에서 Lambda나 ECS 태스크를 정기 실행할 때 쓰는 EventBridge cron 식은 Linux crontab과 비슷해 보이지만 필드 수, ? 사용법, 요일 숫자, 시간대가 조금씩 다릅니다. crontab 감각으로 "0 9 * * 1-5"라고 쓰면 오류가 나고, 요일 숫자를 그대로 옮기면 하루가 밀리며, 시각은 UTC로 해석되어 9시간 어긋나는 등 걸림돌이 겹칩니다. 이 글에서는 EventBridge cron 식의 6개 필드와 ? 규칙, 요일 숫자, L·W·# 사용법, rate 식과의 차이, 규칙과 Scheduler에서 일본 시간을 다루는 방법, crontab·Kubernetes·Spring·systemd와의 변환표까지 정리합니다.
글 읽기 →