WebPiki
tutorial

Cron 표현식 완벽 가이드 — 스케줄 자동화의 기본

Cron 표현식 문법, 예제, 자주 쓰는 패턴을 정리했다. 리눅스 crontab부터 GitHub Actions까지.

스케줄 자동화를 상징하는 시계와 톱니바퀴

서버에서 매일 새벽 3시에 DB 백업을 돌리거나, 매시간 캐시를 갱신하거나, 매주 월요일에 리포트를 보내야 할 때. 이런 반복 작업을 예약하는 데 cron이 쓰인다. 리눅스 시스템 관리의 기본이면서, GitHub Actions, Kubernetes CronJob, AWS EventBridge 등에서도 동일한 표현식을 사용한다.

문법 자체는 간단한데, 플랫폼마다 미묘하게 다른 부분이 있어서 한 번 정리해두면 두고두고 쓸 수 있다.

Cron 표현식 구조

기본 형태는 5개 필드다:

┌───────────── 분 (0-59)
│ ┌───────────── 시 (0-23)
│ │ ┌───────────── 일 (1-31)
│ │ │ ┌───────────── 월 (1-12)
│ │ │ │ ┌───────────── 요일 (0-6, 0=일요일)
│ │ │ │ │
* * * * *

각 필드에 올 수 있는 값:

기호의미예시
*모든 값* * * * * = 매분
숫자특정 값30 9 * * * = 매일 9시 30분
,여러 값0,30 * * * * = 매시 0분과 30분
-범위1-5 = 1~5
/간격*/10 * * * * = 10분마다

자주 쓰는 패턴

시간 기반

* * * * *        매분
*/5 * * * *      5분마다
0 * * * *        매시 정각
0 */2 * * *      2시간마다
30 9 * * *       매일 오전 9시 30분
0 0 * * *        매일 자정
0 9,18 * * *     매일 오전 9시, 오후 6시

요일 기반

0 9 * * 1        매주 월요일 오전 9시
0 9 * * 1-5      평일 오전 9시
0 0 * * 0        매주 일요일 자정
0 18 * * 5       매주 금요일 오후 6시

날짜 기반

0 0 1 * *        매월 1일 자정
0 0 1,15 * *     매월 1일, 15일 자정
0 9 1 1 *        매년 1월 1일 오전 9시
0 0 * * 1#1      매월 첫 번째 월요일 (일부 시스템)

crontab 사용법

리눅스에서 cron 작업을 관리하려면 crontab 명령어를 쓴다.

# 현재 사용자의 cron 작업 목록 보기
crontab -l

# cron 작업 편집
crontab -e

# 특정 사용자의 cron 작업 보기 (root 권한 필요)
sudo crontab -u www-data -l

crontab -e로 열리는 파일에 한 줄에 하나씩 작성한다:

# 매일 새벽 3시에 DB 백업
0 3 * * * /home/user/scripts/backup.sh >> /var/log/backup.log 2>&1

# 10분마다 헬스체크
*/10 * * * * curl -s https://mysite.com/health > /dev/null

>> /var/log/backup.log 2>&1 부분은 출력과 에러를 로그 파일에 기록하라는 뜻이다. 이걸 안 하면 cron이 보내는 메일로 출력이 갈 수 있다.

주의할 점

시간대(timezone). 시스템 시간대를 확인해야 한다. UTC로 설정된 서버에서 0 9 * * *은 한국 시간 오후 6시가 된다.

timedatectl  # 현재 시스템 시간대 확인

환경변수. cron은 사용자의 쉘 환경을 그대로 로드하지 않는다. PATH, HOME 등이 다를 수 있어서, 스크립트에서 절대 경로를 쓰는 게 안전하다.

# crontab 파일 상단에 환경변수 지정 가능
PATH=/usr/local/bin:/usr/bin:/bin

겹침 방지. 작업이 실행 시간보다 오래 걸리면 다음 실행과 겹칠 수 있다. flock으로 잠금을 거는 게 일반적인 해결책이다:

*/5 * * * * flock -n /tmp/myjob.lock /home/user/scripts/slow-job.sh

서머타임(DST). 시스템 시간대가 DST를 적용하는 지역이라면, 새벽 2시 30분에 설정한 작업이 봄에는 건너뛰어지고 가을에는 두 번 실행될 수 있다. 중요한 작업은 UTC 기준으로 설정하면 이 문제를 피할 수 있다.

로그 관리. cron 작업이 많아지면 로그를 한 곳에 모아서 관리하는 게 편하다. 각 작업별로 로그 파일을 분리하고, logrotate로 오래된 로그를 자동 정리하면 디스크 용량 걱정도 줄어든다.

# logrotate 설정 예시 (/etc/logrotate.d/cron-jobs)
/var/log/cron-jobs/*.log {
    weekly
    rotate 4
    compress
    missingok
}

5필드 vs 6필드 vs 7필드

cron 표현식이라고 다 같은 건 아니다. 플랫폼에 따라 필드 수가 다르다:

필드 수구성사용처
5필드분 시 일 월 요일리눅스 crontab, GitHub Actions
6필드초 분 시 일 월 요일Spring @Scheduled, Quartz
6필드 (변형)분 시 일 월 요일 연도AWS EventBridge

Spring Boot에서 @Scheduled(cron = "0 0 9 * * MON")을 쓸 때, 첫 번째 필드가 라는 걸 모르면 헷갈린다. 리눅스 crontab 기준으로 작성했다가 초 필드를 빠뜨리는 실수가 흔하다.

GitHub Actions에서 cron

GitHub Actions의 schedule 트리거도 cron 표현식을 쓴다.

on:
  schedule:
    - cron: '0 9 * * 1'  # 매주 월요일 9시 (UTC)

주의: GitHub Actions의 시간대는 항상 UTC이고, 정확한 시각에 실행된다는 보장이 없다. 부하가 많을 때는 몇 분~수십 분 딜레이가 발생할 수 있다.

Kubernetes CronJob

쿠버네티스에서도 동일한 표현식을 사용한다:

apiVersion: batch/v1
kind: CronJob
metadata:
  name: db-backup
spec:
  schedule: "0 3 * * *"
  jobTemplate:
    spec:
      template:
        spec:
          containers:
          - name: backup
            image: my-backup-image
          restartPolicy: OnFailure

concurrencyPolicy 설정으로 이전 작업이 아직 실행 중일 때의 동작을 제어할 수 있다: Allow(동시 실행 허용), Forbid(새 작업 건너뜀), Replace(이전 작업 종료 후 새로 시작).

AWS EventBridge (CloudWatch Events)

AWS는 cron 표현식에 6개 필드를 쓴다 (연도 추가):

cron(분 시 일 월 요일 연도)
cron(0 9 ? * MON-FRI *)  # 평일 9시 UTC

?는 "지정 안 함"이라는 뜻으로, 일과 요일 중 하나에 반드시 써야 한다. 표준 cron과 살짝 문법이 다르니 AWS 문서를 꼭 참고해야 한다.

디버깅

cron 작업이 안 돌 때 확인할 것들:

# cron 서비스가 돌고 있는지
systemctl status cron

# cron 실행 로그
grep CRON /var/log/syslog

# 스크립트 권한 확인 (실행 권한 있어야 함)
chmod +x /home/user/scripts/backup.sh

권한 문제가 제일 많다. 스크립트를 직접 실행하면 되는데 cron에서만 안 되는 경우, 대부분 PATH나 파일 권한 문제다.

표현식이 내가 원하는 대로 동작하는지 미리 확인하고 싶으면 Cron 표현식 생성기를 써보자. 각 필드를 GUI로 설정하면 다음 실행 시간을 미리 보여준다. */15 3-6 * * 1-5 같은 복잡한 표현식도 한눈에 확인할 수 있다.

실전에서 자주 하는 실수

"매월 31일"에 걸면 안 된다. 0 0 31 * *은 2월, 4월, 6월 등 31일이 없는 달에는 아예 실행되지 않는다. 월말 작업이 필요하면 1일 자정에 실행하고 스크립트 안에서 전날(월말) 데이터를 처리하는 게 안전하다.

0 */2 * * *0 0-23/2 * * *은 다르다. 사실 결과는 같은데, 전자가 훨씬 읽기 쉽다. 가독성 면에서 / 문법을 쓰는 게 팀 협업에 유리하다.

일(day of month)과 요일(day of week)을 동시에 지정하면? 표준 cron에서는 OR 조건으로 동작한다. 0 9 15 * 1은 "15일 또는 월요일"에 실행된다. AND 조건이라고 착각하기 쉬운 부분이다. AWS EventBridge에서는 ? 기호를 강제해서 이 혼동을 원천 차단하고 있다.

systemd timer — cron의 대안

최근 리눅스 배포판에서는 cron 대신 systemd timer를 쓰는 경우도 많다. cron 대비 몇 가지 장점이 있다:

  • 실행 로그가 journalctl로 통합 관리된다
  • 서비스 의존성을 지정할 수 있다 (네트워크 연결 이후 실행 등)
  • OnBootSec, OnCalendar 같은 유연한 스케줄 옵션
# /etc/systemd/system/backup.timer
[Timer]
OnCalendar=*-*-* 03:00:00
Persistent=true

[Install]
WantedBy=timers.target

Persistent=true로 설정하면, 서버가 꺼져있었던 시간대의 놓친 실행을 부팅 후 자동으로 보충한다. cron에는 이런 기능이 없어서 서버 재시작 후 한 번 놓치면 그냥 지나가버린다.

cron이 더 간단하고 범용적이라 완전히 대체되지는 않겠지만, systemd 기반 서버에서는 timer도 고려해볼 만하다.

#cron#자동화#리눅스#DevOps#스케줄링

관련 글