cron 표현식과 KST 시간대 설정 정리
cron 다섯 필드의 의미, 서버가 UTC일 때 KST 기준 일정을 잡는 방법, 일과 요일을 함께 쓸 때의 OR 규칙, 중복 실행을 막는 잠금 방식을 예제와 함께 정리합니다.
다섯 필드를 먼저 읽습니다
cron 표현식은 공백으로 구분된 다섯 필드로 이루어집니다. 필드 순서를 외우기보다, 왼쪽에서 오른쪽으로 "분 시 일 월 요일"이라고 읽는 습관이 실수를 줄입니다.
| 필드 | 허용 값 | 예시 |
|---|---|---|
| 분 | 0-59 | 30 |
| 시 | 0-23 | 15 |
| 일 | 1-31 | 13 |
| 월 | 1-12 (jan, feb 같은 이름도 가능) |
* |
| 요일 | 0-6, 0이 일요일 (sun, mon 같은 이름도 가능) |
1-5 |
*는 모든 값, 1-5는 범위, 1,15는 목록, */15는 간격을 뜻합니다. */15를 분 필드에 쓰면 15분마다 실행됩니다.
cron 표현식 생성기는 입력한 표현식을 영어 문장으로 풀어 보여 주고, 형식이 맞는지 검증합니다. 설명 문장은 cronstrue 라이브러리가 만들고, 검증은 cron-validator가 합니다. 이 도구는 초 필드를 맨 앞에 선택적으로 허용하고, MON-FRI처럼 요일과 월 이름도 받습니다. 다만 표준 crontab 파일은 초 필드를 지원하지 않습니다. 도구에서 6필드로 검증이 통과해도 서버 crontab에 그대로 넣으면 안 됩니다.
도구 입력에서 확인한 값은 다음과 같습니다.
0 0 * * * -> At 00:00, every day
0 9 * * 1-5 -> At 09:00, Monday through Friday
0 0 13 * 5 -> At 00:00, on day 13 of the month, and on Friday
0 0 L * * -> 검증 실패 (L 표기는 허용되지 않음)
0 9 * * 1#2 -> 검증 실패 (# 표기는 허용되지 않음)
L과 #은 Quartz나 일부 스케줄러 문법입니다. 표준 리눅스 cron에는 없으므로, 이 표기를 본 적이 있다면 사용하는 스케줄러가 어떤 문법을 쓰는지부터 확인합니다.
서버가 UTC일 때 KST로 일정 잡기
많은 클라우드 서버와 컨테이너는 기본 시간대가 UTC입니다. 반면 업무 일정은 한국 시간(KST, UTC+9)으로 생각하는 경우가 많습니다. 이 차이를 머릿속에서 계산하면 틀리기 쉽습니다. 변환 규칙을 한 번 정하고 주석으로 남기는 편이 안전합니다.
KST는 UTC보다 9시간 빠릅니다. 한국 표준시는 서머타임을 적용하지 않으므로, 연중 오프셋이 같습니다. 2026년 1월, 7월, 11월의 서울 오프셋을 확인해 모두 GMT+9였습니다.
new Date('2026-11-10T00:30:00+09:00').toISOString()
// '2026-11-09T15:30:00.000Z'
new Date('2026-11-10T09:00:00+09:00').toISOString()
// '2026-11-10T00:00:00.000Z'
이 값을 바탕으로 KST 시각을 UTC cron으로 바꾸면 다음과 같습니다.
- 매일 KST 09:00: UTC 00:00이므로
0 0 * * * - 평일 KST 09:00: 날짜가 바뀌지 않으므로
0 0 * * 1-5 - 평일 KST 09:00부터 18:45까지 15분 간격: UTC 00:00부터 09:45까지이므로
*/15 0-9 * * 1-5 - 매일 KST 00:30: UTC 전날 15:30이므로
30 15 * * * - 매주 월요일 KST 00:30: UTC로는 일요일 15:30이므로
30 15 * * 0
마지막 예제가 특히 자주 틀립니다. 시간만 바꾸고 요일 필드를 1로 그대로 두면 UTC 월요일 15:30에 실행되어, KST로는 화요일 00:30이 됩니다. 날짜 경계를 넘는 일정은 시간과 함께 요일 필드도 같이 옮겨야 합니다.
KST 매월 1일 03:00도 같은 문제가 있습니다. UTC로는 전월 마지막 날 18:00입니다. 달의 마지막 날짜가 28일에서 31일까지 다양해서, 표준 다섯 필드로는 한 줄로 깔끔하게 표현하기 어렵습니다. 이런 경우에는 매일 실행하고 스크립트 안에서 KST 날짜를 확인해 조건에 맞을 때만 작업하는 방법이 낫습니다.
대부분의 cron 구현은 호스트의 시간대를 기준으로 시각을 해석합니다. 서버 시간대를 KST로 바꾸는 것은 다른 프로세스와 로그 시각까지 영향을 주므로 신중해야 합니다. 바꾸지 않는다면 표현식마다 "UTC 기준, KST 몇 시"를 주석으로 적어 두세요.
일과 요일을 함께 쓰면 OR가 됩니다
가장 많이 놓치는 규칙입니다. 표준 cron에서 일(day-of-month) 필드와 요일(day-of-week) 필드를 둘 다 *가 아닌 값으로 지정하면, 두 조건은 AND가 아니라 OR로 동작합니다. 대부분의 리눅스 cron이 이 방식을 따릅니다.
0 0 13 * 5는 "13일이면서 금요일"이 아니라 "13일이거나 금요일"입니다. 이 도구의 설명도 "on day 13 of the month, and on Friday"로 같은 뜻을 보여 줍니다. 2026년 8월에 이 표현식을 적용한 결과를 계산하면 실행일은 7, 13, 14, 21, 28일입니다. 13일은 목요일이고 14일은 금요일이라, 13일과 금요일이 각각 실행일에 들어갑니다.
의도가 "매월 13일 금요일"이라면 cron 표현식만으로는 만들 수 없습니다. 다음 중 하나를 씁니다.
- 요일 필드를
*로 두고, 스크립트에서 요일을 확인한 뒤 실행합니다. - 일 필드를
*로 두고, 스크립트에서 13일인지 확인합니다. - 매주 금요일 실행하는 줄과 매월 13일 실행하는 줄을 따로 씁니다. 이 경우 두 줄이 같은 날 겹치면 중복 실행 문제가 생깁니다.
중복 실행과 잠금
cron은 "정해진 시각에 명령을 실행"할 뿐 "이 작업이 이미 돌고 있는지"는 확인하지 않습니다. 다음 상황에서 같은 작업이 두 번 돌 수 있습니다.
- 서버가 두 대이고 두 서버 모두 같은 crontab을 가지고 있을 때
- 작업이 길어져서 다음 주기 시작 전에 끝나지 않을 때
- 배포 중 이전 프로세스와 새 프로세스가 잠깐 함께 떠 있을 때
한 대의 서버에서 프로세스 중복만 막으면 되는 경우에는 리눅스의 flock 명령을 씁니다. flock -n은 잠금을 얻지 못하면 바로 종료합니다. macOS에는 기본으로 flock이 없으므로 개발 환경과 운영 환경의 차이에 주의하세요.
여러 서버가 같은 작업을 나눠 실행해야 하는 경우에는 파일 잠금이 소용없습니다. DB에 실행 기록 테이블을 두고 (작업 이름, 실행 날짜) 조합에 유니크 제약을 거는 방식이 단순합니다. 먼저 행을 넣는 쪽만 작업을 수행합니다. Redis를 쓴다면 SET key value NX EX처럼 만료 시간이 있는 키를 잠금으로 씁니다. 만료 시간을 넣지 않으면 작업이 중간에 죽었을 때 잠금이 영원히 남습니다.
여기서 실행 날짜를 어느 시간대로 자르는지도 중요합니다. KST 날짜로 키를 만들어야 합니다. UTC 날짜로 만들면 한국 시간 00:00부터 09:00 사이에 키가 하루 전 것으로 잡혀서 같은 날 작업이 두 번 들어갈 수 있습니다.
자주 쓰는 표현식 예제
| 의미 (KST 기준) | UTC 서버에서 쓸 표현식 | 설명 |
|---|---|---|
| 매일 09:00 | 0 0 * * * |
At 00:00, every day |
| 평일 09:00 | 0 0 * * 1-5 |
At 00:00, Monday through Friday |
| 평일 09:00-18:45, 15분 간격 | */15 0-9 * * 1-5 |
15분마다, 00:00-09:59, 월-금 |
| 매일 00:30 | 30 15 * * * |
At 15:30, every day |
| 매주 월요일 00:30 | 30 15 * * 0 |
At 15:30, only on Sunday |
| 매시 정각 | 0 * * * * |
시간대와 무관하게 같음 |
표의 마지막 행처럼 정시 간격은 시간대를 바꿔도 값이 같습니다. 시간이 특정 값으로 고정되는 일정일수록 변환 실수가 생깁니다.
흔한 실수
- 시간만 바꾸고 요일을 그대로 둡니다.
- 일과 요일을 함께 쓰면 AND라고 생각합니다.
- 서버 시간대를 확인하지 않고 로그 시각을 KST로 읽습니다.
- 초 필드가 있는 표현식을 crontab 파일에 넣습니다.
- 잠금 없이 여러 서버에 같은 crontab을 등록합니다.
- 잠금 키를 UTC 날짜로 만듭니다.
- 작업이 실패해도 다음 실행이 정상이라고 가정하고 실패 로그를 남기지 않습니다.
점검 목록
- 표현식을 생성기에 넣어 영어 설명이 기대한 시각과 요일을 말하는지 확인합니다.
- 서버의 시간대를 확인하고, UTC라면 KST 시각을 UTC로 옮긴 결과를 주석으로 남깁니다.
- 날짜 경계를 넘는 일정은 요일 필드까지 다시 계산했는지 확인합니다.
- 일과 요일을 함께 썼다면 OR 규칙으로 실행일을 달력에서 세어 봅니다.
- 여러 서버에서 같은 작업이 도는지 확인하고, 잠금이나 유니크 제약을 둡니다.
- 잠금 키에 쓰는 날짜가 KST 기준인지 확인합니다.
- 작업 실패를 알림이나 로그로 남기는 경로가 있는지 확인합니다.