본문 바로가기

로딩 중...


크론 표현식 빌더

1. 매번 잊어버리는 크론 표현식

서버를 운영하다 보면 크론 표현식을 자주 쓰게 됩니다. 정기 백업, 데이터 집계, 알림 발송처럼 "정해진 시각에 자동으로 돌아야 하는 작업들"을 등록할 때 등 다양한 상황에서 크론 표현식이 사용되고 있습니다.

여기서 문법이 자주 헷갈리고, 까먹기 쉽습니다. 일반적으로 코드 작업처럼 매일 사용되는 것이 아니고, 필요할 때 그때그때 사용하다 보니 쓸 때마다 필드 순서가 헷갈리는 경우가 많습니다. 그래서 온라인에서 빠르고 간편하게 크론 표현식을 점검할 수 있는 도구를 만들었습니다.

또한 누군가가 만들어놓은 스케줄링을 수정 및 점검하기 위해 표현식을 읽어야 하는 상황도 있습니다. 누군가 짜놓은 */15 9-17 * * 1-5를 보고 즉시 "평일 업무 시간에 15분마다"라고 읽을 수 있으면 좋겠지만, 필드 하나하나를 분석해보면서 어떤 뜻인지 해독하는 과정이 필요합니다.

거기에 더해 환경마다 문법이 다르기 때문에 매번 환경마다 다른 문법을 적용해야합니다.. 같은 "매월 1일 자정"이라도 Linux crontab에서는 0 0 1 * *(5필드)이고, Spring이나 Quartz에서는 0 0 0 1 * ?(6필드, 초 포함)이며, AWS EventBridge는 또 다른 형식을 씁니다. 하나의 표현식을 다른 환경에 그대로 옮기면 의도와 전혀 다르게 동작할 수 있습니다.

이런 페인 포인트를 고려하여 어떤 환경이든 대응 가능하며, 즉시 원하는 동작(표현식 빌더 및 해독)이 가능한 크론 표현식 빌더를 만들고 싶어서 해당 도구를 만들게 되었습니다.


2. 크론 표현식에 대해서

a. 정의

크론(Cron)은 Unix 계열 시스템에서 정해진 시각에 작업을 자동 실행하기 위한 스케줄러입니다. 1975년 Brian Kernighan이 처음 작성했고, 이후 Vixie cron 등 다양한 구현이 등장하면서 사실상의 표준이 되었습니다. 표현식의 핵심은 공백으로 구분된 다섯 개의 필드입니다.


┌──────── 분 (0-59)
│ ┌────── 시 (0-23)
│ │ ┌──── 일 (1-31)
│ │ │ ┌── 월 (1-12)
│ │ │ │ ┌ 요일 (0-7, 0과 7은 일요일)
│ │ │ │ │
* * * * *  [command_to_run]

각 필드에 *(모든 값), 단일 숫자, 범위(1-5), 목록(1,3,5), 간격(*/15)을 조합해 원하는 시점을 표현합니다.

필드허용 값자주 쓰는 패턴
분 (Minute)0-590 (정시), */5 (5분마다), 0,30 (정시와 30분)
시 (Hour)0-239 (오전 9시), 9-17 (업무 시간)
일 (Day of Month)1-311 (매월 1일), L (말일)
월 (Month)1-12 또는 JAN-DEC* (매월), 1,7 (1월과 7월)
요일 (Day of Week)0-7 또는 SUN-SAT (0과 7 모두 일요일)1-5 (평일), 0,6 (주말)

Note

요일 필드의 0과 7이 모두 일요일이라는 점은 자주 잊혀집니다. 또한 일부 구현은 0이 일요일이 아니라 월요일인 경우도 있으니, 처음 사용하는 환경이라면 반드시 문서를 한 번 확인해 주시기 바랍니다.

필드 안에서 사용가능한 특수문자의 의미를 아래 표로 한눈에 정리하였습니다. 위의 표는 어떤 필드가 있는지에 대한 설명이고, 아랫표는 필드값을 어떻게 좋바하는가를 설명하는 표입니다.

메타 문자의미예시
*모든 값* (분 필드) → 매분
,여러 값 나열0,15,30,45 → 0분/15분/30분/45분
-범위9-17 → 9시부터 17시까지
/간격(step)*/5 → 5분마다
?사용 안 함 (일/요일 충돌 방지, 일부 구현 한정)Quartz, AWS 환경에서 사용
L마지막 (last)L → 그 달의 마지막 날
W가장 가까운 평일15W → 15일에 가장 가까운 평일

표준 Unix cron은 분 / 시 / 일 / 월 / 요일 다섯 필드만 사용합니다. 그러나 Quartz, AWS, Spring 환경에서는 필드가 앞쪽에 추가되거나, 연도 필드가 뒤쪽에 붙기도 합니다.

환경필드 구성
Unix cron, 대부분의 Linux 시스템분, 시, 일, 월, 요일 (5필드)
Quartz, Spring @Scheduled초, 분, 시, 일, 월, 요일, [연도] (6~7필드)
AWS EventBridge분, 시, 일, 월, 요일, 연도 (6필드, UTC 기준)

같은 표현식을 다른 환경에 그대로 옮기면 동작하지 않을 수 있으니, 운영 환경에 맞는 필드 수를 확인하는 것이 첫 걸음입니다.

b. 왜 쓰는지

서버에서 반복 작업을 실행하는 방법은 여러 가지가 있습니다. 사람이 직접 실행하거나, 애플리케이션 코드 내부에 setInterval 같은 타이머를 두거나, 외부 스케줄러를 사용할 수 있습니다. 그런데 왜 굳이 크론을 쓸까요?

가장 큰 이유는 애플리케이션과 스케줄링의 관심사를 분리할 수 있기 때문입니다. 스케줄 로직이 코드 안에 섞여 있으면 주기를 바꿀 때마다 배포가 필요하지만, 크론은 crontab -e 한 줄로 즉시 수정할 수 있습니다. 애플리케이션이 재시작되거나 크래시가 나더라도 OS 레벨의 크론 데몬은 독립적으로 동작하기 때문에 스케줄이 함께 죽지 않는다는 장점도 있습니다.

또한 크론은 별도 인프라 없이 바로 사용 가능합니다. Linux 시스템이라면 기본으로 설치되어 있고, 별도의 메시지 큐나 워크플로 엔진 없이도 정해진 시각에 작업을 실행할 수 있습니다. 초기 스타트업부터 대규모 시스템까지, 복잡한 의존성 없이 가볍게 스케줄링을 시작할 수 있는 가장 현실적인 선택지입니다.

마지막으로 생태계 호환성이 넓습니다. Unix crontab에서 시작된 표현식 문법은 Kubernetes CronJob, AWS EventBridge, Spring @Scheduled, GitHub Actions 등 거의 모든 스케줄링 환경에서 사실상의 표준으로 채택되었습니다. 한 번 익혀두면 환경이 바뀌어도 같은 개념을 그대로 적용할 수 있습니다.

c. 어떤 상황에서 사용되는지

크론은 "정해진 시각에 자동으로 실행되어야 하는 작업"이 있는 곳이라면 어디든 쓰입니다. 대표적인 사용 상황을 몇 가지 정리하면 다음과 같습니다.

  • 정기 백업: 매일 새벽 데이터베이스 덤프를 생성하거나 로그 파일을 압축·보관하는 작업
  • 데이터 집계 및 리포트: 매시간 또는 매일 특정 시각에 매출 통계, 사용자 지표 등을 계산하고 대시보드에 반영하는 작업
  • 캐시 갱신: 외부 API에서 환율, 날씨, 공공 데이터 등을 주기적으로 가져와 캐시를 업데이트하는 작업
  • 알림 발송: 매일 아침 업무 리마인더, 주간 리포트 이메일, 결제 만료 알림 등을 정해진 시각에 발송하는 작업
  • 시스템 유지보수: 오래된 임시 파일 삭제, 로그 로테이션, 인증서 갱신 체크 등 서버 운영에 필요한 정리 작업
  • 배치 처리: 대량의 데이터를 일괄 변환하거나, 큐에 쌓인 작업을 주기적으로 처리하는 작업

요약하면, 사람이 매번 수동으로 실행하기 번거롭고, 정해진 주기로 반복되는 작업이라면 크론의 적용 대상입니다.


3. 다양한 활용사례

표현식을 만들었으면 이제 실제 시스템에 등록해야 합니다. 환경마다 적용 방법이 미묘하게 다릅니다.

a. Linux crontab

# 현재 사용자 크론 편집
crontab -e

# 등록된 크론 확인
crontab -l

# 예시
0 9 * * 1-5 /usr/local/bin/daily-report.sh >> /var/log/daily-report.log 2>&1

Important

crontab의 PATH는 셸과 다르게 매우 제한적입니다. 명령어는 가급적 절대 경로로 작성하고, 표준 출력과 에러를 반드시 로그 파일로 리다이렉트해야 디버깅이 가능합니다.

b. Kubernetes CronJob

apiVersion: batch/v1
kind: CronJob
metadata:
  name: daily-report
spec:
  schedule: "0 9 * * 1-5"
  timeZone: "Asia/Seoul"  # K8s 1.27+ 에서 지원
  jobTemplate:
    spec:
      template:
        spec:
          containers:
          restartPolicy: OnFailure
            - name: report
            image: my-report:latest

K8s 1.27 이전 버전에서는 시간대 옵션이 없어 클러스터의 컨트롤러 시간대(보통 UTC)가 그대로 적용됩니다. 한국 시간 기준으로 동작하길 원한다면 표현식 자체를 UTC로 환산해야 합니다.

c. AWS EventBridge (구 CloudWatch Events)

cron(0 0 1 * ? *)

AWS의 cron은 6필드(연도 포함)이고 UTC 기준입니다. 또한 일과 요일 필드 중 하나는 반드시 ?로 비워 둬야 합니다. 한국 시간 오전 9시에 실행하고 싶다면 표현식에는 UTC 기준의 0시를 적어야 한다는 점이 자주 발목을 잡는 부분입니다.

d. Spring @Scheduled

@Scheduled(cron = "0 0 9 * * MON-FRI", zone = "Asia/Seoul")
public void dailyReport() {
    // 평일 오전 9시 실행
}

Spring은 6필드(초 포함)이며, zone 옵션으로 시간대를 명시할 수 있습니다.

 


4. 사용시 무엇을 조심해야하는지

a. 일(Day of Month)과 요일(Day of Week) 동시 지정

가장 큰 함정 중 하나입니다. 표준 Unix cron은 일과 요일이 모두 지정되면 OR 조건으로 동작합니다.

0 0 1 * 1

이 표현식의 의도가 "매월 1일이면서 월요일"이라고 생각하기 쉽지만, 실제로는 "매월 1일 또는 매주 월요일"로 동작합니다. 둘 다를 충족시키는 시점은 흔치 않으니, 매주 월요일마다 실행되는 결과를 보게 됩니다.

Important

일과 요일을 동시에 의미 있게 쓰려면 둘 중 하나는 반드시 *로 두는 것이 안전합니다. 둘을 결합한 조건이 필요하다면 cron 대신 애플리케이션 레벨에서 추가 검증을 해야 합니다.

b. 시간대(Time Zone) 문제

크론은 시스템의 로컬 시간대를 기준으로 동작합니다. UTC로 설정된 서버에 0 9 * * *를 등록하면 한국 시간 오후 6시에 실행됩니다.

# 서버 시간대 확인
date
timedatectl

# 컨테이너의 경우 TZ 환경변수 설정
ENV TZ=Asia/Seoul

회사 정책에 따라 다르겠지만, 실무에서는 다음과 같은 패턴이 가장 안전합니다.

  • 모든 서버를 UTC로 통일하고, 크론 표현식을 UTC 기준으로 작성한다.
  • 표현식 옆 주석에 한국 시간 기준 의도를 함께 적어 둔다 (예: # KST 09:00).
  • 시간대를 명시할 수 있는 환경(K8s 1.27+, Spring 등)에서는 명시적으로 지정한다.

c. 일광절약시간(DST)

한국은 DST를 시행하지 않지만, 해외 사용자를 가진 서비스나 글로벌 클라우드 환경에서는 1년에 두 번씩 DST 사고가 발생할 수 있습니다. 봄에 시계가 2시에서 3시로 점프하는 시점에 등록된 크론은 그날 실행되지 않을 수 있고, 가을에 시계가 거꾸로 가는 시점에는 같은 작업이 두 번 실행될 수 있습니다.

d. 다음 실행 시각이 의도와 다를 때

크론을 사용하면서 접할 수 있는 문제들을 정리하였습니다. 크론 적용시 문제가 생기면 다음 내용들을 고려하여 수정을 진행하시기 바랍니다.

1. 시간대 불일치

사실 크론이 의도와 다르게 실행되는 경우의 99.9%는 시간대 문제 일 수 있습니다. 개발자는 한국 시간을 기준으로 표현식을 작성했는데, 서버가 UTC로 동작하고 있다면 9시간씩 어긋난 시점에 실행됩니다. 첫 점검 항목은 항상 date 명령어로 서버 시간대를 확인하는 것입니다.

2. 필드 순서 혼동

도입해서 언급한 사고처럼, 다섯 필드의 순서를 혼동해 의도와 완전히 다른 스케쥴링을 등록하는 경우가 있을 수 있습니다. 특히 리눅스 환경과 Quartz의 환경 표현식이 다른데 이를 인지하지 못하고 사용하는 경우가 있을 수 있습니다.


# Unix cron — 분/시/일/월/요일
0 0 1 * *      # 매월 1일 자정

# Quartz — 초/분/시/일/월/요일
0 0 0 1 * ?    # 매월 1일 자정 (필드가 하나 더 많음)

3. 환경변수 부재

crontab에서 실행되는 명령은 사용자가 셸에서 직접 실행할 때와 환경이 다릅니다. PATH가 매우 제한적이고, HOME이나 사용자별 환경변수가 로드되지 않을 수 있습니다. 셸에서는 잘 돌아가던 스크립트가 크론에서만 실패하는 가장 흔한 원인이 이것입니다.

PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
SHELL=/bin/bash

0 9 * * 1-5 /home/myapp/scripts/daily-report.sh

4. 해결책의 공통점

시간대 문제든, 필드 혼동이든, 환경변수 문제든, 크론 트러블슈팅의 해결책에는 공통점이 있습니다. 다음 실행 시각을 미리 시뮬레이션하고, 실제 실행 결과를 로그로 남기고, 실패 시 알람을 보내는 흐름을 처음부터 갖춰 두는 것입니다. 이 도구의 "다음 10회 실행 시각" 기능이 그 시뮬레이션 단계를 위해 만들어졌습니다.


5. 생각해볼거리

크론 자체는 단순합니다. 어려운 것은 표현식이 아니라, 수많은 스케줄이 누적되었을 때의 운영 부담입니다. 어떤 작업이 언제 실행되는지, 실패했을 때 어떻게 복구할지, 동시에 실행되어도 안전한지 같은 질문이 시스템이 커질수록 점점 더 무거워집니다. 이 도구를 만든 이유도 결국 그 출발점인 "표현식이 의도대로 동작하는지"를 빠르게 확인하기 위해서였습니다.

다만 몇 가지 근본적인 질문은 남습니다.

작업이 동시에 두 번 실행되면 어떻게 할까요? 이전 실행이 예상보다 길어져 다음 실행 시점에도 끝나지 않은 경우, 같은 작업이 동시에 두 번 돌아가면서 데이터가 꼬일 수 있습니다. 이런 시나리오에는 분산 락(Redis SETNX, DB advisory lock 등)이나 단일 인스턴스 보장 옵션(K8s CronJob의 concurrencyPolicy: Forbid)으로 동시 실행을 차단하는 것이 표준 패턴입니다.

실행이 누락되면 어떻게 보충할까요? 서버가 크론 시점에 다운되어 있었다면, 단순한 cron은 그 회차를 그대로 건너뜁니다. 누락 보충이 필요한 도메인이라면 anacron, Quartz의 misfire 정책, 또는 애플리케이션 레벨에서 마지막 실행 시각을 기록해 두고 누락분을 따라잡는 로직을 별도로 구현해야 합니다.

크론에서 더 정교한 스케줄링이 필요해지면? "매월 마지막 영업일", "공휴일 제외 평일", "주가 변동성에 따른 동적 주기" 같은 요구사항이 생기면 크론만으로는 부족합니다. Airflow, Argo Workflows, Temporal 같은 워크플로 엔진으로 옮겨 가는 것을 진지하게 고려할 시점입니다. 크론은 단순함이 강점인 도구이지, 모든 스케줄링 요구를 해결하는 만능 도구가 아니라는 점을 잊지 말아야 합니다.

브라우저 탭 하나로 표현식을 시각적으로 만들고, 다음 실행 시각을 즉시 확인할 수 있다면 — 크론을 등록하기 전에 한 번 더 검증하는 마찰이 사라집니다. 그것만으로도 이 도구의 역할은 충분하다고 생각합니다.

자주 묻는 질문

표현식을 만들었는데 다음 실행 시각이 의도와 다르게 나와요.

가장 흔한 원인은 시간대 불일치입니다. 서버가 UTC로 동작하고 있다면 한국 시간 기준으로 쓴 표현식이 9시간 어긋나게 실행됩니다. 빌더 우측의 다음 실행 시각에서 KST와 UTC를 함께 확인하고, 서버의 실제 시간대에 맞는 값인지 검증해 보세요.

Linux crontab에 넣은 표현식을 Spring @Scheduled에 그대로 쓰면 안 되나요?

안 됩니다. Linux crontab은 분·시·일·월·요일 5개 필드를 사용하지만, Spring @Scheduled는 맨 앞에 초 필드가 추가된 6필드 형식입니다. Linux에서 쓰던 0 9 * * 1-5를 Spring에 그대로 넣으면 필드가 한 칸씩 밀려 의도와 전혀 다르게 동작합니다. AWS EventBridge도 연도 필드가 포함된 6필드라 주의가 필요합니다.

매월 마지막 날에 실행하려면 어떻게 표현하나요?

L 문자를 사용하면 됩니다. 예를 들어 0 0 L * ?는 매월 마지막 날 자정에 실행됩니다. 다만 L은 표준 Unix crontab에서는 지원하지 않고 Quartz, AWS EventBridge, Spring 같은 환경에서만 사용할 수 있습니다. Linux crontab을 쓴다면 28-31 범위와 조건을 조합하거나 스크립트 내부에서 말일 여부를 직접 판단해야 합니다.

표현식은 맞는 것 같은데 crontab에서 스크립트가 실행되지 않아요.

crontab의 실행 환경은 일반 셸과 다릅니다. PATH가 매우 제한적이라 셸에서는 잘 되던 명령이 crontab에서 실패하는 경우가 많습니다. 스크립트 경로를 절대 경로로 작성하고, 출력을 로그 파일로 리다이렉트해서(>> /var/log/script.log 2>&1) 실제 오류 메시지를 확인하는 것이 가장 빠른 디버깅 방법입니다.

이전 실행이 끝나지 않았는데 다음 실행이 겹쳐서 시작됩니다.

크론 자체에는 중복 실행 방지 기능이 없습니다. 단일 서버라면 flock 명령으로 파일 잠금을 걸면 되고, Kubernetes CronJob이라면 concurrencyPolicy: Forbid 옵션으로 이전 Job이 끝나기 전에는 새 Job을 시작하지 않도록 설정할 수 있습니다. 분산 환경에서는 Redis SETNX나 DB Advisory Lock 같은 분산 잠금을 활용하세요.