본문으로 건너뛰기
yutils

Cron 표현식 완벽 가이드 — 문법·타임존·자주 빠지는 함정

Cron 필드별 문법·특수 토큰·Unix/Kubernetes/AWS 의 타임존 동작 차이·말 없이 실패하는 패턴 정리.

약 8분 읽기

cron 은 1970 년대 Unix 에서 처음 나온 스케줄러 문법이다. 50 년이 지난 지금도 Kubernetes CronJob, GitHub Actions, AWS EventBridge, Cloudflare Workers 까지 거의 모든 스케줄러가 cron 표현식을 그대로 받는다. 문법은 간단하지만 구현마다 미묘하게 다르고, 타임존·DST 처리에서 자주 사고가 난다. 이 가이드는 표준 5 필드 문법부터 구현별 차이까지 정리한다.

5 필드 기본 문법

표준 Unix cron 은 5 개 필드를 공백으로 구분한다.

*    *    *    *    *
│    │    │    │    │
│    │    │    │    └── 요일 (0-7, 0과 7 둘 다 일요일)
│    │    │    └─────── 월 (1-12)
│    │    └──────────── 일 (1-31)
│    └───────────────── 시 (0-23)
└────────────────────── 분 (0-59)

특수 기호

기호의미
*모든 값* * * * * — 매분
,목록0,15,30,45 — 0/15/30/45분
-범위9-17 — 9 시부터 17 시
/간격 (step)*/5 — 5 단위 (분 필드면 매 5 분)
?"없음" (AWS, Quartz)일/요일 중 하나만 쓸 때

자주 쓰는 표현

표현의미
* * * * *매분
*/5 * * * *5 분마다
0 * * * *매 시 정각
0 0 * * *매일 0 시 (자정)
0 9 * * 1-5평일 9 시
0 0 1 * *매월 1 일 0 시
0 0 * * 0매주 일요일 0 시
30 2 * * *매일 2:30 AM

직접 표현 만들기는 Cron 표현식 빌더 가 체크박스 UI 로 빠르고, 받은 표현식 해석은 Cron 표현식 분석 가 다음 실행 시각 목록까지 보여준다.

특수 문자열 (nicknames)

대부분의 구현이 5 필드 표현 대신 이름으로도 받는다.

이름동등 표현
@yearly 또는 @annually0 0 1 1 *
@monthly0 0 1 * *
@weekly0 0 * * 0
@daily 또는 @midnight0 0 * * *
@hourly0 * * * *
@reboot시스템 부팅 시 (cron daemon 만)

일 + 요일 — OR 인가 AND 인가

cron 의 가장 큰 함정. 0 0 13 * 5 는 "매월 13 일 그리고 매주 금요일" 일까, "매월 13 일 또는 매주 금요일" 일까?

표준 Unix cron 은 OR. 두 필드 중 하나라도 별표(*) 가 아니면, 어느 한쪽이 매치하면 실행. 위 표현은 "매월 13 일에 한 번 + 매주 금요일마다 한 번". 13 일의 금요일에는 두 조건이 동시 매치하지만 한 번만 실행.

하지만 Quartz (Java 진영) 와 AWS EventBridge 는 AND 이거나 두 필드를 동시에 지정할 수 없게 강제 — 그래서 ? 가 등장. 구현별 동작:

  • Unix cron, crontab — OR
  • Kubernetes CronJob — OR (Unix 기반)
  • GitHub Actions — OR (Unix 기반)
  • AWS EventBridge / CloudWatch — 일과 요일 동시 지정 금지. 하나는 반드시 ?.
  • Quartz — 동시 지정 금지. 하나는 ?.

확장된 6/7 필드 표현

일부 구현은 분 앞에 , 또는 끝에 필드를 추가한다.

  • AWS EventBridge / Quartz — 6 필드 (초·분·시·일·월·요일·년) 또는 7 필드 (+ 년)
  • Spring Scheduling — 6 필드 (초 + 표준 5)

표현식을 다른 시스템으로 옮길 때 필드 수가 같은지 먼저 확인 — Spring 의 0 0 9 * * * 를 Unix 에 박으면 "매월 0 일 0 시 9 분" 처럼 잘못 해석된다.

타임존 — 사고의 80%

Unix cron

시스템 로컬 타임존에서 실행. TZ=Asia/Seoul 환경 변수로 변경 가능. 컨테이너 (UTC 가 기본) 와 호스트의 차이를 모르면 9 시간 어긋난 스케줄이 된다.

Kubernetes CronJob

spec.timeZone 필드 (1.27+ stable). 미지정 시 kube-controller- manager 의 로컬 (대개 UTC). 항상 명시 권장.

GitHub Actions

무조건 UTC. 한국 시간으로 매일 9 시면 0 0 * * * (UTC 0 시 = KST 9 시) 박는다.

AWS EventBridge

cron 은 UTC 고정. 다른 타임존 필요하면 별도 rate() 또는 별도 람다 안에서 시간 변환.

DST (서머타임) 함정

한국은 DST 가 없으므로 영향 없지만 UTC·한국 시간 차이로 시각이 어긋 나는 글로벌 서비스의 경우 DST 가 있는 타임존 (예: America/New_York, Europe/London) 에서 발생.

  • 봄 (시간 건너뜀) — 2:00 → 3:00 으로 점프. 30 2 * * * (매일 2:30) 은 이 날 실행 안 됨.
  • 가을 (시간 반복) — 2:00 가 두 번. 같은 cron 이 두 번 실행될 수 있음.

해결: (1) DST 가 없는 타임존 (UTC, KST 등) 사용 (2) DST 가 있는 타임존 이면 새벽 0-3 시 사이 cron 회피 — 4 시 이후가 안전.

자주 빠지는 함정

1. 30/5 * * * *

"30 분에 시작해서 5 분 간격" 으로 의도했지만, 실제 구현마다 다르다. Vixie cron / cronie 는 30/35/40/45/50/55. 하지만 Java Quartz 는 다른 해석. step 은 */N 으로만 쓰는 게 안전.

2. 31 일 또는 2 월 30 일

0 0 31 * * 는 31 일이 없는 달에는 실행 안 됨. "매월 말일" 은 cron 자체로 표현 불가 — 매일 실행 후 코드에서 $(date +%d) -eq $(date -d tomorrow +%d) 같은 검사가 표준.

3. 동시 실행 / overlap

cron 은 이전 실행이 끝났는지 검사 안 한다. 1 시간 이상 걸리는 작업을 0 * * * * 로 박으면 누적 overlap. 락 파일 또는 flock 사용.

4. 환경 변수 부재

cron daemon 은 minimal 환경에서 실행 — PATH, LANG, HOME 같은 변수가 비어 있다. 셸 스크립트가 node 를 못 찾는 사고가 흔함. cron 라인 자체에 환경 변수 박거나 스크립트 첫 줄에서 source ~/.bashrc.

5. 무음 실패

cron 작업의 stdout/stderr 는 시스템 메일로 가지만, 컨테이너에서는 보통 설정 안 됨. 모든 cron 라인 끝에 >> /var/log/myjob.log 2>&1 를 박거나 healthcheck 서비스 (Healthchecks.io, Dead Man's Snitch) 로 ping.

도구로 직접 보기

요약

  • 표준은 5 필드 (분·시·일·월·요일). AWS·Quartz 는 6/7 필드 — 옮길 때 확인.
  • 일 + 요일 둘 다 지정하면 Unix 는 OR, AWS/Quartz 는 둘 중 하나에 ?.
  • GitHub Actions = UTC, AWS = UTC, Kubernetes = spec.timeZone 명시. 타임존 의존 작업은 항상 명시.
  • DST 가 있는 타임존이면 새벽 0-3 시 cron 회피.
  • 긴 작업은 락 + 로그 + healthcheck 3 종 세트.
가이드 목록으로