cron 표현식 생성기·파서

*/15minute
*hour
*day of month
*month
*day of week

프리셋

다음 실행

현지 시간
@yearly · @annually · @monthly · @weekly

입력한 내용은 저장, 기록, 공유되지 않습니다.

cron 표현식을 입력하면 실제로 다음에 실행될 시각이 표시됩니다. 모든 필드를 검증하고 결코 실행되지 않는 일정은 표시해 줍니다.

사용 방법

  1. 표현식을 입력하거나 프리셋에서 시작하세요.
  2. 다섯 필드의 의미가 아래에 표시됩니다.
  3. 배포 전에 다음 실행 시각을 확인하세요.

다섯 개의 필드

표준 cron 표현식은 공백으로 구분한 다섯 필드로 이루어집니다. 분 0-59, 시 0-23, 일 1-31, 월 1-12, 요일 0-7이며, 요일에서 0과 7은 모두 일요일입니다. 월과 요일은 영어 세 글자 이름으로도 쓸 수 있습니다.

모든 필드에서 같은 네 가지 구성을 쓸 수 있습니다. 별표는 모든 값과 일치합니다. 쉼표로 구분한 목록은 그중 아무 값과 일치합니다. 하이픈 범위는 그 사이의 모든 값과 일치합니다. 슬래시 간격은 범위에서 n번째마다 값을 취하므로, 분 필드의 */15는 15분마다, 0-30/10은 0, 10, 20, 30분입니다.

@hourly, @daily, @weekly, @monthly, @yearly 같은 약칭은 뻔한 표현식으로 풀리며, 잘못 읽을 수가 없으니 맞는 경우라면 쓸 만합니다.

일과 요일 규칙

거의 모든 사람이 걸려 넘어지는 cron 동작이 바로 이것이며, 우연이 아니라 명세에 정해진 것입니다. 일 필드와 요일 필드가 둘 다 제한되어 있으면(둘 다 별표가 아니면) AND가 아니라 OR로 결합됩니다.

그래서 0 0 1 * MON은 '월요일에 해당하는 1일'이 아닙니다. 매달 1일, 그리고 매주 월요일이라는 뜻입니다. 월요일만 원한다면 일 필드는 별표여야 하고, 1일만 원한다면 요일 필드가 별표여야 합니다. 다섯 필드 표현식으로 교집합을 나타내는 표준 방법은 없으며, 흔한 우회책은 합집합으로 예약해 두고 작업 안에서 날짜를 확인하는 것입니다.

다음 실행 시각 목록은 바로 이런 경우를 잡아내기 위해 있습니다. 필드를 읽으면 무엇을 썼는지 알 수 있고, 날짜를 읽으면 실제로 무슨 일이 일어날지 알 수 있습니다.

시간대와 서머타임

여기 표시된 실행 시각은 브라우저의 시간대 기준입니다. 실제로 작업을 실행할 서버에는 자체 시간대가 있고 UTC인 경우가 많으며, 둘 사이의 차이는 작업이 엉뚱한 시각에 실행되는 흔한 원인입니다. 정확한 시각을 믿기 전에 서버 설정이나 스케줄러의 시간대 필드를 확인하세요.

서머타임은 더 날카로운 문제를 더합니다. 서머타임을 시행하는 지역에서 02:30에 예약한 작업은 시계가 앞당겨지는 날 아침에는 아예 실행되지 않습니다. 그 시각이 존재하지 않기 때문입니다. 반대로 시계가 되돌려지는 날 아침에는 두 번 실행됩니다. 청구, 정산, 메일 발송처럼 실행 누락이나 중복이 문제가 되는 작업은 UTC로 예약하거나, 전환이 일어나는 새벽 1~3시를 피하세요.

머신이 절전 상태이거나 꺼져 있는 동안 놓친 작업을 어떻게 처리할지도 구현마다 다릅니다. 건너뛰는 것도 있고, 깨어날 때 실행하는 것도 있고, 놓친 실행을 한꺼번에 몰아서 모두 실행하는 것도 있습니다.

다른 곳에서 만나게 될 확장

표준은 다섯 필드이며, Linux의 cron, Kubernetes CronJob, 대부분의 호스팅 스케줄러가 받아들이는 형식입니다. 널리 쓰이는 몇몇 시스템은 이를 확장했고, 그 사이에서 복사한 표현식이 항상 그대로 통하지는 않습니다.

Quartz와 이를 쓰는 Spring 스케줄러는 앞에 초를, 끝에 선택적인 연도를 더해 여섯 또는 일곱 필드를 씁니다. 그래서 다섯 필드 표현식을 붙여 넣으면 모든 값이 한 칸씩 밀려 해석되어, 실패하는 대신 엉뚱한 시각에 실행됩니다. Quartz는 마지막을 뜻하는 L, 가장 가까운 평일을 뜻하는 W, 그달의 n번째 요일을 뜻하는 #도 추가합니다. Linux의 표준인 Vixie cron은 @reboot를 추가합니다. AWS EventBridge는 여섯 필드를 쓰며, 두 날짜 필드 중 정확히 하나는 별표가 아니라 물음표여야 합니다.

이 도구가 검증하고 설명하는 것은 다섯 필드 표준입니다. 사용하는 스케줄러가 위의 것 중 하나라면, 표현식이 그곳에서도 같은 뜻이라고 가정하기 전에 해당 문서를 확인하세요.

cron 작업이 실제로 실행되는 환경

예약 작업은 스케줄 표기 때문보다 환경 때문에 실패하는 경우가 더 많습니다. cron은 셸 프로파일을 읽지 않으므로 PATH가 최소한으로, 대개 /usr/bin과 /bin 정도로만 설정되고, 터미널에서 잘 되던 명령이 command not found로 실패합니다. 확실한 습관은 인터프리터와 스크립트를 모두 절대 경로로 쓰거나, crontab 맨 위에서 PATH를 명시적으로 지정하는 것입니다.

작업 디렉터리는 프로젝트가 아니라 사용자의 홈이라서 상대 경로는 엉뚱한 곳을 가리킵니다. 로그인 셸이 설정하는 환경 변수는 물론, 셸 훅으로 읽어 들이던 .env의 값도 전혀 없습니다. 게다가 crontab에서 퍼센트 기호는 특별한 의미를 가져 줄바꿈으로 바뀝니다. %Y 같은 날짜 형식 문자열을 이스케이프하지 않으면 명령이 조용히 망가지는 이유가 바로 이것입니다.

나머지 절반은 출력입니다. 작업이 stdout이나 stderr로 쓴 내용은 메일 전송 에이전트가 있으면 소유자에게 메일로 가고, 없으면 그냥 버려집니다. 작업이 몇 달씩 조용히 실패하는 이유가 여기에 있습니다. 실제로 들여다보는 로그 파일로 리다이렉트하거나, 실패할 때뿐 아니라 성공할 때도 작업이 모니터링 시스템에 보고하도록 만드십시오.

중복 실행, 멱등성, 그리고 건너뛴 실행

cron은 이전 실행이 끝났는지와 상관없이 일정대로 작업을 시작합니다. 5분 걸리는 작업을 5분 주기로 돌리면 언젠가 두 개가 동시에 돌고, 그다음엔 세 개가 됩니다. 그리고 그 장애는 스케줄 오류가 아니라 작업이 중복 처리되거나 교착 상태에 빠지는 형태로 드러나는 것이 보통입니다. 표준적인 해법은 잠금입니다. Linux의 flock, 데이터베이스의 advisory lock, 공유 저장소의 리스 같은 것들이죠. 다만 중간에 죽은 실행이 이후 모든 실행을 막지 않도록 타임아웃이 필요합니다.

각 작업은 두 번 실행돼도 해가 없도록 설계하십시오. 대개 이는 일정이 처리 범위를 정한다고 가정하지 말고 무엇을 처리했는지 기록한다는 뜻입니다. 아직 처리하지 않은 행을 골라 처리 표시를 남기고, 두 번째 실행에서는 할 일이 없도록 만드는 것입니다.

정반대 문제는 건너뛴 실행입니다. 머신이 절전 상태였거나 프로세스가 죽었다면, 구현에 따라 그 회차를 건너뛰기도 하고, 늦게라도 실행하기도 하며, 놓친 회차를 한꺼번에 몰아서 실행하기도 합니다. Anacron은 항상 켜져 있지 않은 머신을 다루기 위해 존재하고, 컨테이너 스케줄러도 저마다 정책이 있으니 의존하기 전에 읽어 볼 가치가 있습니다.

crontab을 대신하는 요즘 선택지

systemd 타이머는 요즘 대부분의 Linux 배포판에서 cron을 대체하며, cron에는 없던 것들을 제공합니다. 의존성 순서, 리소스 제한, journal로의 자동 로깅, 동시 몰림을 피하기 위한 무작위 지연, 그리고 놓친 실행을 다음 부팅 때 수행하는 Persistent=true 같은 것들입니다. 대가는 설정량입니다. 한 줄이 아니라 타이머 유닛과 서비스 유닛 두 개의 파일이 필요하니까요.

Kubernetes CronJob은 같은 다섯 필드 문법을 그대로 쓰면서 동시 실행 정책, 이력 보관 개수, 데드라인 처리를 더했습니다. 이는 앞에서 설명한 문제들을 명시적으로 다룰 수 있게 만든 것에 정확히 해당합니다. 클라우드 이벤트 버스, CI 스케줄, 서버리스 타이머 같은 관리형 스케줄러는 보통 정확히 한 번이 아니라 최소 한 번 전달을 약속하므로, 멱등성의 책임은 다시 작업 쪽으로 넘어옵니다.

애플리케이션 내부 작업이라면 지연 메시지를 쓰는 큐가 어떤 스케줄러보다 잘 맞는 경우가 많습니다. 재시도와 가시성, 모니터링이 나중에 덧붙이는 것이 아니라 큐에 처음부터 딸려 오기 때문입니다.

사람들이 실제로 쓰는 일정

15분마다는 */15 * * * *입니다. 매일 밤 02:30은 30 2 * * *, 평일 09:00은 0 9 * * 1-5, 매월 1일 자정은 0 0 1 * *입니다. 반면 매월 말일을 나타내는 표준 표현은 아예 없어서, 매일 실행하되 내일이 1일이 아니면 그냥 종료하는 것이 흔한 우회책입니다.

나중에도 읽히는 crontab을 만드는 습관이 두 가지 있습니다. 모든 것을 정시에 몰지 말고 부하를 분산하십시오. 7 3 * * *는 0 3 * * *보다 쓰기 어렵지 않으면서도 그 머신의 다른 모든 작업과 경쟁하는 일을 피하게 해 줍니다. 그리고 각 줄에 무엇을 하는 작업이고 누가 담당인지 주석을 달아 두십시오. crontab은 그 항목을 추가한 사람의 기억보다 오래 살아남기 때문입니다.

일정이 정말 복잡하다면, 영리한 표현식 하나보다 단순한 항목 두 개로 나누는 편이 대개 더 명확합니다. 1년 동안 조용히 돌던 작업이 갑자기 멈춘 새벽 세 시에 필요한 것은 바로 그 명확함입니다.

작업이 돌기는 했는지 알아내기

예약 작업의 전형적인 실패는 침묵입니다. 요란하게 실패하는 작업은 그날 바로 고쳐지지만, 실행이 멈춘 작업은 아무 출력도 내지 않아서, 그 작업이 만들어 내던 주간 보고서가 세 주째 오지 않을 때까지 아무도 알아채지 못합니다.

이를 해결하는 패턴이 데드맨 스위치 모니터링입니다. 작업이 성공하면 모니터링 서비스로 핑을 보내고, 예상한 시간 안에 핑이 오지 않으면 서비스가 경보를 울립니다. 경보의 방향을 뒤집는 이 방식은 호스트 다운, 삭제된 crontab, 실패한 배포, 재부팅 후 돌아오지 않은 머신까지 전부 잡아냅니다. 밖에서 보면 이 모두가 똑같은 모습, 즉 아무 일도 일어나지 않는 모습이기 때문입니다.

실행되었다는 사실만이 아니라 그 실행이 무엇을 했는지 기록하십시오. 시작 시각, 종료 시각, 처리한 레코드 수, 종료 상태를 한 줄로 남기면 막연한 의심이 답으로 바뀝니다. 작업이 돌았음을 아는 것과 제대로 일했음을 아는 것의 차이가 여기에 있습니다. 야간 작업이 한 달 내내 조용히 0건을 처리했다면, 언제부터 그랬는지 알려 주는 증거는 대개 로그에 남은 그 건수뿐입니다.

자주 묻는 질문

설명만이 아니라 다음 실행 시각을 보여주는 이유는?
표현식이 문법적으로 완벽해도 의도한 의미가 아닐 수 있기 때문입니다. 다섯 필드를 읽으면 무엇을 썼는지 알 수 있지만, 다음 여섯 개의 날짜를 보면 실제로 무슨 일이 일어날지 알 수 있습니다. 스케줄 버그는 대개 그 차이에서 발견됩니다.
일(날짜)과 요일을 둘 다 지정하면 어떻게 되나요?
AND가 아니라 OR로 결합됩니다. 즉 "0 0 1 * MON"은 매월 1일 그리고 매주 월요일에 실행되며, 1일인 월요일에만 실행되는 것이 아닙니다. 거의 모두가 오해하지만 이는 규격에 정의된 동작이며, 다음 실행 시각을 보면 바로 드러납니다.
어떤 cron 문법을 지원하나요?
표준 5필드(분, 시, 일, 월, 요일)를 지원합니다. 와일드카드, 범위, 목록, 간격, 월과 요일의 세 글자 이름은 물론 @daily, @hourly, @weekly, @monthly, @yearly 축약형도 사용할 수 있습니다. 일요일은 0 또는 7로 쓸 수 있습니다.
표시되는 시각의 시간대는 무엇인가요?
브라우저의 시간대입니다. 서버의 cron은 그 서버 시간대에서 동작하며 대개 UTC라 사용자의 컴퓨터와 다른 경우가 많으니, 정확한 시각이 중요하다면 서버 설정을 확인하세요.
표현식이 절대 실행되지 않는다고 나오는 이유는?
2월 30일처럼 성립할 수 없는 조합이 있기 때문입니다. 빈 목록을 보여주는 대신 그 사실을 직접 알려줍니다.

마지막 업데이트:

문제 신고

진행 단계, 예상 결과, 실제 결과를 적어 주세요. 비밀 정보와 개인 정보는 삭제해 주세요.

관련 도구