본문 바로가기
개발 팁

cron 표현식 완전 정복 — 스케줄링 실전 치트시트

by 루까(Luka) 2026. 3. 20.
반응형

왜 이 글을 쓰나

cron은 쓸 때마다 찾아본다. "매주 월요일 오전 9시"를 cron으로 어떻게 쓰더라? 0 9 * * 1 맞지? 아니면 0 9 * * MON이었나? 6자리 cron이면 앞에 초(second) 필드가 붙는다는데, 내가 쓰는 라이브러리가 5자리인지 6자리인지도 헷갈린다.

자동화 스크립트 짤 때마다 반복하는 이 검색을 끝내려고 정리했다. 기본 구조부터 자주 쓰는 패턴, Linux crontab 사용법, Node.js 라이브러리까지 한 곳에 모았다.


cron 표현식 기본 구조

5자리 vs 6자리

형식 필드 구성 사용처
5자리 분 시 일 월 요일 Linux crontab, GitHub Actions
6자리 초 분 시 일 월 요일 Spring Scheduler, Quartz, AWS EventBridge
7자리 초 분 시 일 월 요일 연도 Quartz (연도 선택 포함)

가장 흔히 쓰는 건 5자리다. Linux crontab과 대부분의 온라인 cron 생성기가 5자리 기준이다. Node.js의 node-cron도 5자리, node-schedule은 6자리(초 포함)를 지원한다. 라이브러리 문서를 먼저 확인하는 게 맞다.

각 필드 범위

필드 범위 특이사항
초 (second) 0–59 6자리 이상에서만
분 (minute) 0–59  
시 (hour) 0–23  
일 (day of month) 1–31  
월 (month) 1–12 또는 JAN–DEC  
요일 (day of week) 0–7 또는 SUN–SAT 0과 7 모두 일요일

요일에서 0과 7이 둘 다 일요일인 건 알고 있어야 한다. 0 * * * 00 * * * 7 모두 매주 일요일이다.

특수 문자 정리

문자 의미 예시
* 모든 값 * → 매분, 매시, 매일
, 여러 값 나열 1,3,5 → 1, 3, 5번째
- 범위 지정 9-18 → 9시부터 18시까지
/ 간격 지정 */5 → 5단위마다
L 마지막 일 필드에서 L → 말일, 요일 필드에서 5L → 마지막 금요일
W 가장 가까운 평일 15W → 15일에서 가장 가까운 평일
# n번째 특정 요일 5#2 → 두 번째 금요일

L, W, #은 Quartz 등 일부 라이브러리에서만 지원한다. Linux crontab에는 없다.


자주 쓰는 패턴 모음

바로 복붙할 수 있도록 정리했다. 5자리 기준이다.

기본 패턴

설명 cron 표현식
매분 실행 * * * * *
매시 정각 0 * * * *
매일 자정 (00:00) 0 0 * * *
매일 오전 6시 0 6 * * *
매주 월요일 자정 0 0 * * 1
매월 1일 자정 0 0 1 * *
매년 1월 1일 자정 0 0 1 1 *

간격 패턴

설명 cron 표현식
5분마다 */5 * * * *
10분마다 */10 * * * *
30분마다 */30 * * * *
2시간마다 0 */2 * * *
매일 6시간마다 (0, 6, 12, 18시) 0 */6 * * *

특정 시간대 패턴

설명 cron 표현식
평일(월~금)만 자정 0 0 * * 1-5
주말(토~일)만 자정 0 0 * * 6,0
업무 시간(9~18시) 매시 정각 0 9-18 * * *
평일 업무 시간 매시 정각 0 9-18 * * 1-5
특정 시각 여러 개 (9시, 13시, 18시) 0 9,13,18 * * *
매일 오전 9시 30분 30 9 * * *

실전 조합 패턴

설명 cron 표현식
매주 월요일 오전 9시 0 9 * * 1
매월 마지막 날 자정 (Quartz) 0 0 L * ?
매주 두 번째 금요일 (Quartz) 0 0 ? * 5#2
평일 오전 8시 30분 30 8 * * 1-5
매 15분마다 (0, 15, 30, 45분) 0,15,30,45 * * * *

Linux crontab 사용법

기본 명령어

# crontab 편집기 열기
crontab -e

# 현재 등록된 cron 목록 보기
crontab -l

# 모든 cron 삭제 (주의: 되돌릴 수 없음)
crontab -r

# 특정 사용자의 crontab 편집 (root 권한 필요)
crontab -u username -e

crontab 파일 형식

# 분 시 일 월 요일 명령어
* * * * * /path/to/command

# 환경 변수 설정 (상단에 작성)
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
MAILTO=admin@example.com  # 실행 결과를 이메일로 수신

# 예시
0 2 * * * /home/user/backup.sh                     # 매일 새벽 2시에 백업
*/5 * * * * /usr/local/bin/health-check.sh          # 5분마다 헬스체크
0 9 * * 1 /home/user/weekly-report.sh >> /var/log/report.log 2>&1

출력 리다이렉트

# 표준 출력 + 에러를 파일에 저장
0 2 * * * /home/user/backup.sh >> /var/log/backup.log 2>&1

# 출력 버리기 (로그 불필요할 때)
*/5 * * * * /usr/local/bin/check.sh > /dev/null 2>&1

# 표준 출력만 파일에, 에러는 버리기
0 * * * * /home/user/script.sh >> /var/log/output.log 2>/dev/null

2>&1은 표준 에러(2)를 표준 출력(1)으로 합치는 것이다. 둘 다 같은 파일에 남기고 싶을 때 쓴다.

/etc/crontab vs crontab -e

항목 crontab -e /etc/crontab
대상 현재 사용자 시스템 전체
사용자 필드 없음 있음 (명령어 앞에 사용자 지정)
편집 권한 일반 사용자 가능 root만
사용 시점 개인 스크립트 시스템 레벨 작업
# /etc/crontab 형식 (사용자 필드 추가됨)
# 분 시 일 월 요일 사용자 명령어
0 2 * * * root /usr/local/bin/backup.sh

Node.js에서 cron

node-cron (5자리)

npm install node-cron
const cron = require('node-cron');

// 매분 실행
cron.schedule('* * * * *', () => {
  console.log('1분마다 실행');
});

// 매일 자정 DB 백업
cron.schedule('0 0 * * *', async () => {
  console.log('DB 백업 시작:', new Date().toISOString());
  await backupDatabase();
});

// 평일 오전 9시 리포트 발송
cron.schedule('0 9 * * 1-5', async () => {
  await sendDailyReport();
}, {
  timezone: 'Asia/Seoul'   // 타임존 지정 (중요!)
});

// cron 작업 제어
const task = cron.schedule('*/30 * * * *', () => {
  checkServerHealth();
}, {
  scheduled: false  // 즉시 시작하지 않음
});

task.start();   // 수동 시작
task.stop();    // 중지
task.destroy(); // 제거

node-schedule (6자리 + 더 유연한 규칙)

npm install node-schedule
const schedule = require('node-schedule');

// 6자리 cron (초 분 시 일 월 요일)
const job = schedule.scheduleJob('0 0 0 * * *', () => {
  console.log('매일 자정 (초 포함 6자리)');
});

// 날짜 객체로 특정 시각 지정
const date = new Date(2026, 11, 25, 9, 0, 0); // 크리스마스 오전 9시
const christmasJob = schedule.scheduleJob(date, () => {
  console.log('메리 크리스마스!');
});

// 규칙 객체로 더 세밀하게
const rule = new schedule.RecurrenceRule();
rule.dayOfWeek = [1, 2, 3, 4, 5]; // 월~금
rule.hour = 9;
rule.minute = 0;
rule.tz = 'Asia/Seoul';

schedule.scheduleJob(rule, () => {
  sendMorningBriefing();
});

// 작업 취소
job.cancel();

언어별 cron 라이브러리 비교

언어 라이브러리 cron 자리 타임존 지원 특징
Node.js node-cron 5자리 ✓ (옵션) 가볍고 단순, 가장 많이 쓰임
Node.js node-schedule 5/6자리 날짜 객체, 규칙 객체 지원
Node.js cron (npm) 6자리 초 단위 스케줄링
Python APScheduler 6자리 cron/interval/date 3가지 방식
Python schedule 자체 DSL X (별도 설정) 간단한 자체 문법 (every(5).minutes.do(...))
Java Spring Scheduler 6자리 @Scheduled 어노테이션
Java Quartz 7자리 엔터프라이즈급, L/W/# 지원
PHP Laravel Task Scheduling 5자리 ->cron(), ->everyFiveMinutes() 체이닝
PHP cron-expression 5자리 X 파서만 제공

5자리 vs 6자리, crontab vs 라이브러리 비교

5자리 vs 6자리

항목 5자리 (분 시 일 월 요일) 6자리 (초 분 시 일 월 요일)
최소 단위 1분 1초
사용처 Linux crontab, GitHub Actions, node-cron Spring, Quartz, node-schedule
초 단위 스케줄링 불가 가능
가독성 높음 상대적으로 낮음 (초 필드 앞에 붙어 혼동)
온라인 생성기 호환 대부분 호환 라이브러리마다 다름

Linux crontab vs 애플리케이션 내장 스케줄러

항목 Linux crontab 애플리케이션 스케줄러
설정 방식 시스템 파일/명령어 코드 내 설정
의존성 OS 수준 라이브러리
서버 재시작 후 지속 ✓ (자동) ✓ (앱 재시작 필요)
타임존 제어 서버 타임존 따름 코드에서 직접 설정
로그 관리 별도 설정 필요 앱 로깅 시스템 활용
분산 환경 단일 서버 기준 락 메커니즘 필요
적합한 경우 단순 쉘 스크립트, OS 유지보수 앱 로직과 연계된 작업

실전 활용 예시

DB 백업 자동화

# Linux crontab: 매일 새벽 2시 MySQL 백업
0 2 * * * /home/ubuntu/scripts/backup-db.sh >> /var/log/db-backup.log 2>&1
#!/bin/bash
# backup-db.sh
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_DIR="/home/ubuntu/backups"
DB_NAME="myapp"

mkdir -p $BACKUP_DIR

mysqldump -u root -p"$DB_PASSWORD" $DB_NAME | gzip > "$BACKUP_DIR/backup_$DATE.sql.gz"

# 7일 이상 된 백업 삭제
find $BACKUP_DIR -name "*.sql.gz" -mtime +7 -delete

echo "[$DATE] 백업 완료: backup_$DATE.sql.gz"

캐시 초기화

// Node.js: 매시 정각 Redis 캐시 초기화
const cron = require('node-cron');
const redis = require('redis');

const client = redis.createClient();

cron.schedule('0 * * * *', async () => {
  const keys = await client.keys('cache:product:*');
  if (keys.length > 0) {
    await client.del(keys);
    console.log(`[${new Date().toISOString()}] 캐시 ${keys.length}개 초기화`);
  }
}, {
  timezone: 'Asia/Seoul'
});

리포트 이메일 발송

// 매주 월요일 오전 9시 주간 리포트 발송
cron.schedule('0 9 * * 1', async () => {
  const report = await generateWeeklyReport();

  await sendEmail({
    to: 'team@example.com',
    subject: `[주간 리포트] ${new Date().toLocaleDateString('ko-KR')}`,
    html: report,
  });

  console.log('주간 리포트 발송 완료');
}, {
  timezone: 'Asia/Seoul'
});

삽질 기록

1. 타임존 문제 — 서버는 UTC, 나는 KST

가장 많이 당하는 문제다. 서버 타임존이 UTC로 설정된 경우, 0 9 * * *은 한국 시각 오전 9시가 아니라 오전 6시 (UTC+9 기준 오후 6시) 에 실행된다. 서버 타임존이 UTC면 KST로 맞추려면 9시간 빼야 한다.

// 잘못된 설정 (서버 UTC, 의도는 KST 오전 9시)
cron.schedule('0 9 * * *', task); // 실제로는 UTC 기준 9시 = KST 오후 6시

// 올바른 설정 1: timezone 옵션 사용
cron.schedule('0 9 * * *', task, { timezone: 'Asia/Seoul' });

// 올바른 설정 2: UTC로 계산해서 표기
cron.schedule('0 0 * * *', task); // UTC 0시 = KST 9시

배포 환경마다 서버 타임존이 다를 수 있다. cron 라이브러리의 timezone 옵션을 항상 명시적으로 설정하는 게 안전하다.

2. **/1의 차이

# 이 둘은 동일하다
* * * * *    # 매분
*/1 * * * *  # 1분마다 = 매분

*/1은 "1씩 증가하는 모든 값"이라서 *와 완전히 같다. 헷갈리는 경우가 있어서 기록해둔다. */2는 0, 2, 4... 짝수 분에 실행, */5는 0, 5, 10, 15... 5의 배수 분에 실행된다.

3. 서버 재시작 후 cron이 날아감

crontab -e로 등록한 cron은 서버 재시작 이후에도 살아있다. 문제는 Node.js 앱 내부에 node-cron으로 등록한 작업이다. 앱이 내려가면 cron도 같이 내려간다. 앱이 pm2나 systemd로 관리되지 않으면 서버 재시작 후 cron이 실행되지 않는다.

# pm2로 앱 관리 + 서버 재시작 후 자동 재개
pm2 start app.js --name "myapp"
pm2 startup  # 서버 재시작 후 pm2 자동 시작 설정
pm2 save     # 현재 프로세스 목록 저장

앱 내 cron을 쓴다면 프로세스 매니저 설정이 필수다.

4. 분산 환경에서 cron 중복 실행

서버 인스턴스가 여러 대인 환경에서 각 서버에 동일한 cron이 등록되면 작업이 중복으로 실행된다. DB 집계나 이메일 발송 같은 건 두 번 실행되면 안 된다.

해결 방법은 여러 가지다.

// Redis 분산 락 사용 (node-cron + redis)
const cron = require('node-cron');
const redis = require('redis');

const client = redis.createClient();

cron.schedule('0 9 * * 1', async () => {
  const lockKey = 'cron:weekly-report';
  const lockAcquired = await client.set(lockKey, '1', {
    NX: true,    // 키가 없을 때만 설정
    EX: 3600,    // 1시간 후 자동 해제
  });

  if (!lockAcquired) {
    console.log('다른 서버에서 이미 실행 중');
    return;
  }

  try {
    await sendWeeklyReport();
  } finally {
    await client.del(lockKey);
  }
}, { timezone: 'Asia/Seoul' });

단순하게 해결하려면 cron을 특정 서버(또는 전용 worker 서버) 하나에서만 실행하는 방법도 있다.


마무리

cron 표현식 자체는 어렵지 않다. 5자리 기준으로 분 시 일 월 요일을 외우고, *는 전체, /는 간격, ,는 나열, -는 범위라는 것만 기억하면 대부분의 케이스를 커버한다.

실전에서 자주 만나는 함정은 표현식 문법보다 타임존, 라이브러리별 자리수 차이, 분산 환경 중복 실행이다. 특히 타임존은 한 번이라도 틀려보면 잊어버리지 않는다.

crontab guru에서 표현식을 입력하면 실행 일정을 텍스트로 설명해준다. 작성한 표현식이 맞는지 확인할 때 쓰기 좋다.


기술 스택: cron, crontab, node-cron, node-schedule, Linux, Redis
참고 도구: crontab guru, Cron Expression Generator

반응형