[Linux] cron으로 리눅스 주기적 반복 작업(배치) 예약 및 백업 자동화

2026. 6. 14. 20:15·Linux
SMALL
시나리오

새벽 3시, DB 백업 스크립트가 실행됐어야 하는데 다음 날 아침에 확인해보니 결과물이 없습니다. crontab에 분명히 등록했는데 왜 안 됐는지 알 방법조차 없습니다. 로그도 없고, 에러도 없고, 그냥 조용히 실패한 겁니다. 반대로 디스크가 서서히 가득 차가는데, 매주 정리해야 할 로그 파일을 지우는 작업을 매번 수동으로 하고 있다면? cron을 제대로 이해하면 이 두 문제가 모두 해결됩니다.

Cron & 작업 스케줄링

서버는 잠들지 않습니다. 새벽 2시에 데이터베이스를 백업하고, 매주 일요일마다 오래된 로그를 정리하고, 매시간 디스크 사용량을 체크하는 일들을 사람이 직접 할 수는 없습니다. 이런 반복 작업을 자동화하는 것이 cron과 systemd timer입니다.

cron은 Unix 시스템에서 수십 년간 사용되어 온 작업 스케줄러입니다. 간단하고 강력하지만, 몇 가지 함정이 있어 처음 접하는 사람들이 자주 실수를 겪습니다. 이 챕터에서는 cron의 동작 원리부터 실무에서 바로 쓸 수 있는 예제, 그리고 흔한 트러블슈팅까지 모두 다룹니다.

— — —

학습 목표

  • cron 데몬이 어떻게 동작하는지 내부 원리를 이해할 수 있다
  • crontab 5-필드 문법(분 시 일 월 요일)을 정확히 읽고 쓸 수 있다
  • 특수 문자(*, ,, -, /)와 단축어(@daily, @reboot)를 활용할 수 있다
  • crontab -e, crontab -l, crontab -r의 차이와 위험성을 이해할 수 있다
  • /etc/crontab, /etc/cron.d/, 사용자 crontab의 차이를 설명할 수 있다
  • /etc/cron.hourly/ 등 디렉토리 기반 스케줄링을 활용할 수 있다
  • cron 실행 로그를 확인하고 문제를 진단할 수 있다
  • systemd timer 유닛을 작성하고 cron과 비교할 수 있다
  • PATH 누락, 이메일 출력, 실수 삭제 등 실무 트러블슈팅을 해결할 수 있다

— — —

이번 챕터에서 배울 것
1cron 데몬 동작 원리와 crontab 파일 구조를 이해할 수 있다
25-필드 문법(분 시 일 월 요일)과 특수 문자를 완전히 분석할 수 있다
3crontab -e / -l / -r 명령을 올바르게 사용하고 실수를 방지할 수 있다
4/etc/crontab, /etc/cron.d/, cron.hourly/daily/weekly/monthly 디렉토리를 활용할 수 있다
5cron 실행 로그를 확인하고 PATH·환경변수 문제를 트러블슈팅할 수 있다
6systemd timer 유닛을 작성하고 cron과 비교할 수 있다
실습 환경 준비
cron 데몬 상태 확인
systemctl status cron  # 또는 systemctl status crond
현재 사용자 crontab 목록 확인
crontab -l
cron 실행 로그 확인
grep CRON /var/log/syslog | tail -20  # 또는 journalctl -u cron
crontab -r 실수 방지 팁
crontab 삭제 전 반드시 crontab -l > ~/crontab.bak 으로 백업할 것
개념
cron 데몬 동작 원리
cron 데몬 동작 원리 — crontab 5-field 구문과 cron 실행 흐름

새벽 3시에 DB 백업 스크립트가 실행됐어야 하는데, 다음 날 아침에 확인해보니 결과물이 없습니다. crontab에 분명히 등록했는데 왜 안 됐는지 알 방법이 없습니다. cron이 실제로 어떻게 돌아가는지 이해하면 — 어떤 사용자 권한으로 실행되는지, 환경변수가 왜 일반 터미널과 다른지, 실행 기록이 어디에 남는지 — 이런 무음 실패(silent failure) 상황을 추적할 수 있습니다.

cron은 데몬(daemon) 프로세스입니다. 시스템이 부팅될 때 시작되어 백그라운드에서 계속 실행되며, 1분마다 깨어나 실행할 작업이 있는지 확인합니다.

crond 프로세스

bash# cron 데몬이 실행 중인지 확인
systemctl status cron        # Debian/Ubuntu
systemctl status crond       # RHEL/CentOS/Rocky

# 프로세스 목록에서 확인
ps aux | grep cron
root       847  0.0  0.0  16568  1024 ?   Ss   Mar25   0:00 /usr/sbin/cron -f

동작 흐름

cron 데몬은 시작할 때 모든 crontab 파일을 메모리에 읽어들입니다. 이후 매 분마다 현재 시각과 각 작업의 스케줄을 비교해, 일치하는 작업을 별도의 자식 프로세스로 실행합니다.

[crond 데몬]
    │
    ├─ 부팅 시: /etc/crontab, /etc/cron.d/*, 사용자 crontab 로드
    │
    └─ 매 분마다:
          ├─ 현재 시각 확인 (예: 02:00, 월요일)
          ├─ 일치하는 항목 탐색
          └─ fork() → 자식 프로세스에서 명령어 실행

crontab 파일이 저장되는 위치

사용자가 crontab -e로 편집한 내용은 시스템 어딘가에 저장됩니다. 직접 편집하면 안 되지만, 위치를 알아두면 유용합니다.

bash# 사용자 crontab 저장 위치
ls /var/spool/cron/crontabs/    # Debian/Ubuntu
ls /var/spool/cron/             # RHEL/CentOS

# 현재 사용자 crontab 파일
cat /var/spool/cron/crontabs/$(whoami)

중요: /var/spool/cron/ 파일을 직접 편집하지 마세요. 반드시 crontab -e 명령을 사용해야 합니다. 직접 수정하면 cron이 변경 사항을 인식하지 못하거나, 파일이 손상될 수 있습니다.

crontab 변경 감지

최신 cron 구현체들은 파일 변경을 자동으로 감지합니다. crontab -e로 저장하면 데몬이 파일 변경을 감지하고 자동으로 재로드합니다. 별도로 systemctl reload cron을 실행할 필요가 없습니다.

— — —

개념
crontab 문법 완전 분석
crontab 5-field 문법 완전 분석 — 분·시·일·월·요일 특수문자

*/5 * * * *와 0 */5 * * *는 전혀 다른 의미입니다. 하나는 5분마다, 다른 하나는 5시간 정각마다 실행됩니다. cron 문법의 5개 시각 필드에서 *, */n, ,, - 각각이 어떤 의미인지 헷갈리면 의도와 다른 시각에 작업이 실행되고, 운이 나쁘면 너무 자주 돌아서 서버에 부하를 줍니다. 한 번 제대로 파악해두면 이후 crontab 작성은 몇 초면 됩니다.

crontab의 각 줄은 6개 필드로 구성됩니다. 처음 5개는 실행 시각을 정의하고, 마지막은 실행할 명령어입니다.

┌───────────── 분 (0-59)
│ ┌─────────── 시 (0-23)
│ │ ┌───────── 일 (1-31)
│ │ │ ┌─────── 월 (1-12)
│ │ │ │ ┌───── 요일 (0-7, 0과 7 모두 일요일)
│ │ │ │ │
* * * * * /path/to/command

기본 예제

bash# 매일 새벽 2시 30분에 실행
30 2 * * * /usr/local/bin/backup.sh

# 매시간 정각에 실행
0 * * * * /usr/local/bin/health-check.sh

# 매주 월요일 오전 9시에 실행
0 9 * * 1 /usr/local/bin/weekly-report.sh

# 매월 1일 자정에 실행
0 0 1 * * /usr/local/bin/monthly-cleanup.sh

# 매일 오전 6시와 오후 6시에 실행
0 6,18 * * * /usr/local/bin/twice-daily.sh

특수 문자 4가지

`*` (별표) — 모든 값

해당 필드의 모든 유효한 값을 의미합니다.

bash# 매 분마다 실행 (분 필드가 *)
* * * * * /path/to/command

# 매일 오전 8시 (분=0, 시=8, 나머지는 모두)
0 8 * * * /path/to/command

`,` (쉼표) — 목록

여러 값을 나열합니다.

bash# 매주 월요일과 수요일과 금요일 오전 9시
0 9 * * 1,3,5 /path/to/command

# 1월, 4월, 7월, 10월 1일 자정 (분기별)
0 0 1 1,4,7,10 * /path/to/command

`-` (하이픈) — 범위

연속된 값의 범위를 지정합니다.

bash# 월~금(1-5) 오전 9시
0 9 * * 1-5 /path/to/command

# 오전 9시~오후 5시 매시간 정각 (업무 시간)
0 9-17 * * 1-5 /path/to/command

`/` (슬래시) — 간격

"N마다"를 의미합니다. */N 또는 범위/N 형태로 씁니다.

bash# 15분마다 실행
*/15 * * * * /path/to/command

# 2시간마다 실행 (0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22시)
0 */2 * * * /path/to/command

# 오전 8시~오후 8시 사이 2시간마다
0 8-20/2 * * * /path/to/command

복합 예제

bash# 평일 업무시간(9-18시)에 30분마다
*/30 9-18 * * 1-5 /path/to/command

# 매월 첫째 주 월요일과 셋째 주 월요일에 실행 (날짜와 요일을 같이 쓰면 OR 조건)
0 9 1-7,15-21 * 1 /path/to/command

주의: 일(day of month)과 요일(day of week)을 둘 다 `*`가 아닌 값으로 지정하면 OR 조건으로 처리됩니다. 예를 들어 0 0 15 * 5는 "매월 15일 또는 매주 금요일 자정"입니다. 이는 많은 사람이 착각하는 함정입니다.

@단축어

자주 쓰이는 패턴을 위한 단축어입니다.

단축어 동등한 표현 설명
@reboot (해당 없음) 시스템 부팅 후 1회 실행
@yearly / @annually 0 0 1 1 * 매년 1월 1일 자정
@monthly 0 0 1 * * 매월 1일 자정
@weekly 0 0 * * 0 매주 일요일 자정
@daily / @midnight 0 0 * * * 매일 자정
@hourly 0 * * * * 매시간 정각
bash# 부팅 시 VPN 클라이언트 시작
@reboot /usr/local/bin/start-vpn.sh

# 매일 자정에 백업
@daily /usr/local/bin/daily-backup.sh

# 매주 일요일 자정에 정리 작업
@weekly /usr/local/bin/weekly-cleanup.sh

— — —

crontab 명령어 사용법

실습 전 디렉토리와 예제 파일을 먼저 준비합니다.

bash# 실습 디렉토리 준비
mkdir -p /tmp/linux/part5/exam_222 && cd /tmp/linux/part5/exam_22

# 백업 스크립트 생성
cat > /tmp/linux/part5/exam_222/backup.sh << 'EOF'
#!/bin/bash
BACKUP_DIR="/tmp/linux/part5/exam_222/backups"
mkdir -p "$BACKUP_DIR"
DATE=$(date +%Y%m%d_%H%M%S)
tar -czf "$BACKUP_DIR/backup_$DATE.tar.gz" /tmp/linux/part5/exam_222/*.sh 2>/dev/null
echo "$(date): Backup completed → backup_$DATE.tar.gz" >> "$BACKUP_DIR/backup.log"
EOF

# 정리 스크립트 생성
cat > /tmp/linux/part5/exam_222/cleanup.sh << 'EOF'
#!/bin/bash
# 7일 이상 된 로그 파일 삭제
find /tmp/linux/part5/exam_222/backups -name "*.tar.gz" -mtime +7 -delete
echo "$(date): Cleanup done" >> /tmp/linux/part5/exam_222/backups/cleanup.log
EOF
chmod +x /tmp/linux/part5/exam_222/*.sh

이제 실습을 진행합니다.

crontab 편집: crontab -e

crontab -e는 현재 사용자의 crontab을 편집기로 엽니다. 기본 편집기는 시스템 설정에 따라 다르지만, 보통 nano 또는 vi입니다.

bash# 현재 사용자의 crontab 편집
crontab -e

# 특정 사용자의 crontab 편집 (root만 가능)
sudo crontab -e -u www-data
sudo crontab -e -u deploy

편집기가 열리면 맨 아래에 새 줄을 추가합니다. 파일을 저장하고 닫으면 cron이 자동으로 새 설정을 로드합니다.

bash# 편집기 저장 후 확인 메시지
crontab: installing new crontab

편집기 변경 방법

bash# 기본 편집기를 nano로 변경
export VISUAL=nano
export EDITOR=nano

# 또는 영구 설정 (~/.bashrc에 추가)
echo 'export VISUAL=nano' >> ~/.bashrc
echo 'export EDITOR=nano' >> ~/.bashrc
source ~/.bashrc
crontab 목록 확인: crontab -l

현재 등록된 crontab 전체 내용을 출력합니다. 편집기를 열지 않고 확인만 할 때 사용합니다.

bash# 현재 사용자의 crontab 목록
crontab -l

# 특정 사용자의 crontab 목록 (root만 가능)
sudo crontab -l -u deploy

# crontab이 없을 경우 출력
# no crontab for username

출력 예시

# 매일 새벽 2시 DB 백업
0 2 * * * /usr/local/bin/db-backup.sh >> /var/log/db-backup.log 2>&1

# 매주 일요일 로그 정리
0 3 * * 0 /usr/local/bin/log-cleanup.sh >> /var/log/cleanup.log 2>&1

# 15분마다 헬스 체크
*/15 * * * * /usr/local/bin/health-check.sh

crontab 백업 (중요!)

bash# 현재 crontab을 파일로 백업
crontab -l > ~/crontab-backup-$(date +%Y%m%d).txt

# 백업 파일 확인
cat ~/crontab-backup-20260326.txt

crontab을 수정하기 전에 항상 백업을 만들어 두는 습관을 들이세요.

crontab 삭제 주의: crontab -r

crontab -r은 현재 사용자의 crontab 전체를 즉시, 확인 없이 삭제합니다. 실수로 실행하면 복구가 매우 어렵습니다.

bash# 경고: 이 명령은 crontab 전체를 삭제합니다!
crontab -r

# 삭제 전 확인을 요구하는 안전한 버전
crontab -ri
# remove crontab for user? (y/n)

`-r`과 `-e`는 키보드에서 인접해 있어 실수하기 쉽습니다. 항상 -ri를 사용하거나, 삭제 전에 백업을 만드세요.

bash# 안전한 삭제 방법 (확인 포함)
crontab -ri

# 또는 삭제 전 백업 후 삭제
crontab -l > /tmp/crontab-backup.txt && crontab -r
echo "백업 저장됨: /tmp/crontab-backup.txt"

실수로 삭제했을 때 복구 방법은 이 챕터 아래 트러블슈팅 섹션에서 다룹니다.

관찰 포인트

— — —

시스템 전체 crontab 구조

crontab 사용 보안 제어: /etc/cron.allow와 /etc/cron.deny

리눅스 시스템은 특정 사용자에게만 crontab을 생성할 수 있는 권한을 제한하는 강력한 보안 통제 메커니즘을 제공합니다.

  1. /etc/cron.allow 파일이 존재하는 경우 (화이트리스트):
    • 오직 이 파일에 등록된 사용자만 crontab 명령을 수행할 수 있습니다.
    • 이 경우 /etc/cron.deny 파일은 완전히 무시됩니다.
  2. /etc/cron.allow 파일이 없고 /etc/cron.deny 파일만 존재하는 경우 (블랙리스트):
    • cron.deny 파일에 등록된 사용자를 제외한 모든 사용자가 crontab을 사용할 수 있습니다.
  3. 두 파일이 모두 존재하지 않는 경우:
    • 시스템 환경(Ubuntu/CentOS 등)에 따라 root만 가능하게 제한되거나, 모든 사용자가 사용 가능하도록 기본 동작합니다.
/etc/crontab와 /etc/cron.d/ 활용

사용자 crontab 외에도 시스템 전체에 적용되는 crontab 파일들이 있습니다.

/etc/crontab — 시스템 crontab

bashcat /etc/crontab
SHELL=/bin/sh
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin

# m h dom mon dow user  command
17 *   * * *   root    cd / && run-parts --report /etc/cron.hourly
25 6   * * *   root    test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.daily )
47 6   * * 7   root    test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.weekly )
52 6   1 * *   root    test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.monthly )

/etc/crontab은 사용자 crontab과 달리 6번째 필드에 실행 사용자가 있습니다.

분 시 일 월 요일 사용자 명령어
0  2  *  *  *   root    /usr/local/bin/backup.sh

/etc/cron.d/ — 패키지별 crontab

애플리케이션 패키지들이 독립적인 cron 파일을 /etc/cron.d/에 설치합니다. /etc/crontab과 동일한 문법(사용자 필드 포함)을 사용합니다.

bash# cron.d 파일 목록 확인
ls /etc/cron.d/

# 예: unattended-upgrades가 설치한 cron 파일
cat /etc/cron.d/apt-compat
bash# 직접 패키지용 cron 파일 생성 예시
sudo vim /etc/cron.d/myapp

# 파일 내용
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

# myapp 데이터 수집 - 5분마다
*/5 * * * * www-data /opt/myapp/bin/collect-metrics.sh >> /var/log/myapp/metrics.log 2>&1

세 가지 방식 비교

방식 사용자 필드 용도 편집 방법
crontab -e 없음 (파일 소유자가 실행) 개인/서비스 계정 작업 crontab -e
/etc/crontab 있음 시스템 전체 작업 sudo vim /etc/crontab
/etc/cron.d/*.conf 있음 패키지/앱별 작업 sudo vim /etc/cron.d/파일명
/etc/cron.hourly, daily, weekly, monthly 디렉토리 활용

시간 간격이 고정된 작업은 crontab 문법 없이 스크립트를 디렉토리에 넣기만 하면 됩니다. run-parts 명령이 해당 디렉토리의 모든 실행 가능한 파일을 실행합니다.

bash# 각 디렉토리 확인
ls /etc/cron.hourly/
ls /etc/cron.daily/
ls /etc/cron.weekly/
ls /etc/cron.monthly/

스크립트 배치 방법

bash# 매일 실행할 스크립트 작성
sudo vim /etc/cron.daily/cleanup-tmp

#!/bin/bash
# /tmp 파일 중 7일 이상 된 것 삭제
find /tmp -type f -mtime +7 -delete
find /tmp -type d -empty -delete

# 실행 권한 부여 (필수!)
sudo chmod +x /etc/cron.daily/cleanup-tmp

# 확인
ls -la /etc/cron.daily/cleanup-tmp

중요한 제약 사항

bash# 파일명에 점(.)이 있으면 run-parts가 실행하지 않습니다!
# 잘못된 예:
/etc/cron.daily/backup.sh    # .sh 확장자 때문에 실행 안 됨!

# 올바른 예:
/etc/cron.daily/backup       # 확장자 없음
/etc/cron.daily/db-backup    # 하이픈은 허용

언제 실행되는지 확인

/etc/cron.daily가 정확히 언제 실행되는지는 /etc/crontab의 설정에 따라 다릅니다. anacron이 설치된 경우 정확한 시각보다는 "하루 이내에 한 번"이 보장됩니다.

bash# anacron 설정 확인 (지연 실행 설정)
cat /etc/anacrontab

— — —

cron 로그 확인

cron 실행 로그 확인 및 디버깅

cron이 작업을 실행했는지, 오류가 있었는지 확인하는 방법입니다.

syslog에서 cron 로그 찾기

bash# Ubuntu/Debian: syslog에서 CRON 항목 찾기
grep CRON /var/log/syslog

# 실시간 모니터링
grep CRON /var/log/syslog | tail -f

# journald 사용 시스템 (RHEL/CentOS/Fedora)
journalctl -u crond
journalctl -u cron --since "today"
journalctl -u cron -f    # 실시간

# 특정 시간대 로그
journalctl -u cron --since "2026-03-26 02:00" --until "2026-03-26 03:00"

로그 출력 예시

Mar 26 02:00:01 myserver CRON[12345]: (root) CMD (/usr/local/bin/db-backup.sh)
Mar 26 02:00:03 myserver CRON[12345]: (root) MAIL (mailed 0 byte of output to root)
Mar 26 03:00:01 myserver CRON[12389]: (deploy) CMD (/usr/local/bin/health-check.sh)

각 줄의 의미:

  • CRON[PID]: cron 데몬이 실행한 자식 프로세스 ID
  • (user) CMD: 어떤 사용자가 어떤 명령을 실행했는지
  • (user) MAIL: 출력 결과를 이메일로 전송했는지 (바이트가 0이면 출력 없음)

작업 출력을 직접 로그 파일로 저장

bash# crontab에 로그 리다이렉션 추가
0 2 * * * /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1

# 날짜 포함 로그
0 2 * * * /usr/local/bin/backup.sh >> /var/log/backup-$(date +\%Y\%m\%d).log 2>&1

`%`(퍼센트) 이스케이프: crontab 파일에서 %는 줄 바꿈 문자로 해석됩니다. 명령어 안에서 %를 사용하려면 반드시 \%로 이스케이프해야 합니다.

bash# 잘못된 예 (% 때문에 오동작)
0 2 * * * echo $(date +%Y-%m-%d) >> /var/log/test.log

# 올바른 예 (\ 이스케이프)
0 2 * * * echo $(date +\%Y-\%m-\%d) >> /var/log/test.log

— — —

systemd timer

개념
systemd timer: 현대적인 작업 스케줄링
systemd .timer 파일 구조와 systemctl list-timers 출력

cron으로 등록한 배치 작업이 실패했는데 로그가 없어서 원인을 모를 때, systemd timer를 썼다면 journalctl -u db-backup.service로 바로 확인할 수 있습니다. cron의 가장 큰 단점은 작업 출력이 기본적으로 메일로만 가고, 로그를 별도로 리다이렉션하지 않으면 흔적이 남지 않는다는 점입니다. systemd timer는 cron보다 설정이 조금 더 복잡하지만, journald와 통합된 로깅, 작업 간 의존성 설정, 실행 실패 시 재시도 정책 등 운영 환경에서 필요한 기능을 갖추고 있습니다.

systemd가 있는 시스템에서는 cron 대신 systemd timer를 사용할 수 있습니다. timer는 .service 유닛과 .timer 유닛 두 파일로 구성됩니다.

기본 구조

timer를 만들려면 두 개의 유닛 파일이 필요합니다:

  1. 실제 작업을 정의하는 .service 파일
  2. 실행 스케줄을 정의하는 .timer 파일

.service 유닛 예시

bash# /etc/systemd/system/db-backup.service
sudo vim /etc/systemd/system/db-backup.service
ini[Unit]
Description=데이터베이스 일일 백업
After=network.target

[Service]
Type=oneshot
User=backup
ExecStart=/usr/local/bin/db-backup.sh
StandardOutput=journal
StandardError=journal

Type=oneshot: 한 번 실행하고 종료되는 작업에 사용합니다.

.timer 유닛 예시

bash# /etc/systemd/system/db-backup.timer
sudo vim /etc/systemd/system/db-backup.timer
ini[Unit]
Description=매일 새벽 2시 DB 백업 타이머
Requires=db-backup.service

[Timer]
OnCalendar=*-*-* 02:00:00
AccuracySec=1min
Persistent=true

[Install]
WantedBy=timers.target

주요 Timer 옵션

옵션 설명 예시
OnCalendar 절대 시각 기반 *-*-* 02:00:00, Mon *-*-* 09:00:00
OnBootSec 부팅 후 N초/분/시간 후 OnBootSec=5min
OnUnitActiveSec 마지막 실행 후 N초/분/시간 후 OnUnitActiveSec=1h
AccuracySec 실행 시각의 허용 오차 AccuracySec=1s (정확도 높임)
Persistent=true 시스템이 꺼진 동안 실행됐어야 할 작업을 재시작 시 실행 —

OnCalendar 표현식

bash# 매일 새벽 2시
OnCalendar=*-*-* 02:00:00

# 매주 일요일 오전 3시
OnCalendar=Sun *-*-* 03:00:00

# 평일 오전 9시
OnCalendar=Mon..Fri *-*-* 09:00:00

# 매월 1일 자정
OnCalendar=*-*-01 00:00:00

# 15분마다
OnCalendar=*:0/15

# 표현식 검증
systemd-analyze calendar "Mon..Fri *-*-* 09:00:00"

timer 활성화 및 관리

bash# 데몬 재로드
sudo systemctl daemon-reload

# timer 활성화 (부팅 시 자동 시작)
sudo systemctl enable db-backup.timer

# timer 시작
sudo systemctl start db-backup.timer

# timer 상태 확인
systemctl status db-backup.timer

# 모든 timer 목록
systemctl list-timers --all

# timer 로그 확인
journalctl -u db-backup.service

`systemctl list-timers` 출력 예시

NEXT                         LEFT          LAST                         PASSED       UNIT                  ACTIVATES
Thu 2026-03-27 02:00:00 KST  21h left      Wed 2026-03-26 02:00:01 KST  2h 59min ago db-backup.timer       db-backup.service
Thu 2026-03-27 03:00:00 KST  22h left      Wed 2026-03-26 03:00:01 KST  1h 59min ago log-cleanup.timer     log-cleanup.service

cron vs systemd timer 비교

항목 cron systemd timer
설정 방법 단일 crontab 줄 .service + .timer 두 파일
로그 syslog (제한적) journald (풍부한 메타데이터)
실패 처리 없음 OnFailure=, Restart= 가능
의존성 없음 After=, Requires= 등 지원
놓친 실행 처리 anacron으로 별도 처리 Persistent=true로 내장 지원
복잡도 낮음 높음
적합한 경우 간단한 반복 작업 복잡한 의존성, 상세 모니터링 필요 시

— — —

실무 예제

실무 맥락

운영 데이터베이스를 매일 자동으로 백업하는 실무 예제입니다. PostgreSQL 기준이지만, MySQL도 유사하게 적용할 수 있습니다.

백업 스크립트 작성

bashsudo vim /usr/local/bin/db-backup.sh
bash#!/bin/bash
# PostgreSQL 일일 백업 스크립트
# /usr/local/bin/db-backup.sh

set -euo pipefail

# 설정
DB_USER="postgres"
DB_NAME="production_db"
BACKUP_DIR="/var/backups/postgresql"
RETENTION_DAYS=30
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="${BACKUP_DIR}/${DB_NAME}_${DATE}.sql.gz"
LOG_FILE="/var/log/db-backup.log"

# 로그 함수
log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" >> "$LOG_FILE"
}

# 백업 디렉토리 생성
mkdir -p "$BACKUP_DIR"

log "백업 시작: ${DB_NAME}"

# pg_dump로 백업 (gzip 압축)
if pg_dump -U "$DB_USER" "$DB_NAME" | gzip > "$BACKUP_FILE"; then
    BACKUP_SIZE=$(du -sh "$BACKUP_FILE" | cut -f1)
    log "백업 성공: ${BACKUP_FILE} (${BACKUP_SIZE})"
else
    log "오류: 백업 실패"
    exit 1
fi

# 오래된 백업 삭제 (30일 이상)
DELETED_COUNT=$(find "$BACKUP_DIR" -name "*.sql.gz" -mtime +${RETENTION_DAYS} -print | wc -l)
find "$BACKUP_DIR" -name "*.sql.gz" -mtime +${RETENTION_DAYS} -delete
log "오래된 백업 ${DELETED_COUNT}개 삭제 (${RETENTION_DAYS}일 초과)"

log "백업 완료"
bash# 스크립트 실행 권한 부여
sudo chmod +x /usr/local/bin/db-backup.sh

# 수동으로 먼저 테스트
sudo -u postgres /usr/local/bin/db-backup.sh
cat /var/log/db-backup.log

crontab 등록

bash# postgres 사용자로 crontab 편집
sudo crontab -e -u postgres

# 또는 root crontab에 사용자 지정
sudo crontab -e
bash# postgres 사용자 crontab에 추가
# 매일 새벽 2시 DB 백업
0 2 * * * /usr/local/bin/db-backup.sh >> /var/log/db-backup.log 2>&1

또는 /etc/cron.d/에 파일로 추가:

bashsudo vim /etc/cron.d/postgresql-backup
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

# 매일 새벽 2시 PostgreSQL 백업
0 2 * * * postgres /usr/local/bin/db-backup.sh >> /var/log/db-backup.log 2>&1

백업 확인

bash# 백업 파일 목록
ls -lh /var/backups/postgresql/

# 로그 확인
tail -20 /var/log/db-backup.log

# cron 실행 기록
grep "db-backup" /var/log/syslog
# 또는
journalctl -u cron --since "today" | grep db-backup

MySQL/MariaDB 버전

bash#!/bin/bash
# MySQL 백업 버전 핵심 부분
mysqldump -u root --password="$DB_PASS" \
    --all-databases \
    --single-transaction \
    --routines \
    --triggers \
    | gzip > "$BACKUP_FILE"
실무 맥락

서버 로그 파일이 쌓이면 디스크가 가득 찰 수 있습니다. 매주 자동으로 오래된 로그를 정리하는 스크립트를 만들어 봅니다.

참고: /etc/logrotate.d/를 사용하는 logrotate가 일반적인 로그 로테이션 도구이지만, 커스텀 로그 디렉토리나 특수한 처리가 필요할 때는 직접 스크립트를 작성합니다.

로그 정리 스크립트

bashsudo vim /usr/local/bin/log-cleanup.sh
bash#!/bin/bash
# 주간 로그 정리 스크립트
# /usr/local/bin/log-cleanup.sh

set -euo pipefail

LOG_DIRS=(
    "/var/log/myapp"
    "/var/log/nginx"
    "/opt/services/logs"
)
KEEP_DAYS=14        # 14일 이상된 로그 삭제
COMPRESS_DAYS=3     # 3일 이상된 로그 압축
REPORT_FILE="/var/log/log-cleanup-report.log"

log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$REPORT_FILE"
}

log "===== 주간 로그 정리 시작 ====="
log "실행 시각: $(date)"

TOTAL_FREED=0

for DIR in "${LOG_DIRS[@]}"; do
    if [[ ! -d "$DIR" ]]; then
        log "디렉토리 없음, 건너뜀: $DIR"
        continue
    fi

    log "처리 중: $DIR"

    # 3일 이상된 .log 파일 gzip 압축
    COMPRESSED=0
    while IFS= read -r -d '' file; do
        gzip -f "$file" && COMPRESSED=$((COMPRESSED + 1))
    done < <(find "$DIR" -name "*.log" -not -name "*.gz" -mtime +${COMPRESS_DAYS} -print0)
    log "  압축: ${COMPRESSED}개 파일"

    # 14일 이상된 파일 삭제 전 크기 계산
    BEFORE=$(du -sb "$DIR" 2>/dev/null | cut -f1 || echo 0)

    # 14일 이상된 압축 로그 삭제
    DELETED=$(find "$DIR" -name "*.gz" -mtime +${KEEP_DAYS} -print | wc -l)
    find "$DIR" -name "*.gz" -mtime +${KEEP_DAYS} -delete

    # 빈 디렉토리 정리
    find "$DIR" -type d -empty -not -path "$DIR" -delete 2>/dev/null || true

    AFTER=$(du -sb "$DIR" 2>/dev/null | cut -f1 || echo 0)
    FREED=$(( (BEFORE - AFTER) / 1024 / 1024 ))
    TOTAL_FREED=$((TOTAL_FREED + FREED))

    log "  삭제: ${DELETED}개 파일, ${FREED}MB 확보"
done

log "===== 정리 완료: 총 ${TOTAL_FREED}MB 확보 ====="

# 현재 디스크 사용량 리포트
log "현재 디스크 사용량:"
df -h / /var /opt 2>/dev/null | tee -a "$REPORT_FILE" || true
bashsudo chmod +x /usr/local/bin/log-cleanup.sh

# 수동 테스트
sudo /usr/local/bin/log-cleanup.sh
cat /var/log/log-cleanup-report.log

crontab 등록

bashsudo crontab -e
bash# 매주 일요일 새벽 3시 로그 정리 (DB 백업 완료 후)
0 3 * * 0 /usr/local/bin/log-cleanup.sh >> /var/log/log-cleanup.log 2>&1

디스크 사용량 모니터링과 함께 사용

bash# 디스크 90% 초과 시 알림 (5분마다 체크)
*/5 * * * * df -h / | awk 'NR==2 {gsub(/%/,"",$5); if($5>=90) print "경고: 디스크 사용량 "$5"%%"}' | logger -t disk-monitor

# 알림 확인
grep "disk-monitor" /var/log/syslog

— — —

트러블슈팅

트러블슈팅
오류

cron에서 가장 흔한 문제입니다. 터미널에서는 잘 되는 스크립트가 cron에서는 "command not found" 오류와 함께 실패합니다.

원인

cron은 매우 제한된 환경에서 실행됩니다. 터미널에서 실행할 때의 PATH와 cron의 PATH가 다릅니다.

bash# 터미널에서의 PATH (풍부한 경로)
echo $PATH
# /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/snap/bin

# cron의 기본 PATH (매우 제한적)
# /usr/bin:/bin

/usr/local/bin에 설치된 node, python3, pg_dump 등을 cron에서 찾지 못하면 실패합니다.

진단 방법

bash# cron에서 환경 변수 덤프하기 (임시 디버깅)
* * * * * env > /tmp/cron-env.txt

# 1분 후 확인
cat /tmp/cron-env.txt
SHELL=/bin/sh
HOME=/root
LOGNAME=root
PATH=/usr/bin:/bin      ← 이것이 cron의 PATH

해결 방법 1: crontab 파일 상단에 PATH 설정

bashcrontab -e
bash# crontab 파일 상단에 추가
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

# 이제 아래 작업들은 위의 PATH를 사용
0 2 * * * /usr/local/bin/backup.sh
*/5 * * * * health-check.sh    # PATH에 있으므로 전체 경로 불필요

해결 방법 2: 스크립트 내부에서 절대 경로 사용

bash#!/bin/bash
# 절대 경로 사용
/usr/bin/python3 /opt/myapp/script.py
/usr/local/bin/pg_dump -U postgres mydb > /tmp/backup.sql
/usr/bin/docker compose ps

해결 방법 3: 스크립트 첫 줄에서 PATH 확장

bash#!/bin/bash
# 스크립트 시작 시 PATH 명시적 설정
export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

# 이후 일반 명령어 사용 가능
node --version
python3 script.py

기타 환경 변수 관련 문제

PATH 외에도 다음 환경 변수들이 문제가 될 수 있습니다:

bash# crontab에서 환경 변수 설정
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOME=/root
LANG=ko_KR.UTF-8        # 한국어 로케일이 필요한 경우
DB_PASSWORD=secret      # 환경 변수로 비밀번호 전달

0 2 * * * /usr/local/bin/backup.sh
트러블슈팅
오류

cron은 기본적으로 작업의 표준 출력(stdout)이 있으면 시스템 메일로 전송하려 합니다. 메일 서버 설정에 따라 /var/mail/root에 쌓이거나, 메일 전송 실패 오류가 로그에 가득 차게 됩니다.

문제 확인

bash# 시스템 메일함 확인
cat /var/mail/root
# 또는
mail

# 로그에서 메일 관련 오류 확인
grep CRON /var/log/syslog | grep -i mail

해결 방법 1: 출력을 /dev/null로 버리기

bash# 표준 출력과 표준 에러 모두 버리기
0 2 * * * /usr/local/bin/backup.sh > /dev/null 2>&1

# 표준 에러만 버리기 (표준 출력은 메일로)
0 2 * * * /usr/local/bin/backup.sh 2>/dev/null

# 표준 출력은 로그 파일로, 에러는 버리기
0 2 * * * /usr/local/bin/backup.sh >> /var/log/backup.log 2>/dev/null

해결 방법 2: MAILTO 환경 변수로 메일 비활성화

bashcrontab -e
bash# 전체 crontab에 대해 메일 비활성화
MAILTO=""

0 2 * * * /usr/local/bin/backup.sh
*/15 * * * * /usr/local/bin/health-check.sh

MAILTO=""로 설정하면 모든 작업의 출력이 메일로 전송되지 않습니다. 하지만 스크립트 자체에서 출력이 없도록 설계하거나, 로그 파일로 리다이렉션하는 것이 더 좋은 방식입니다.

해결 방법 3: 특정 주소로 메일 받기

로그가 필요한 경우 MAILTO에 이메일 주소를 설정합니다:

bashMAILTO="admin@example.com"

0 2 * * * /usr/local/bin/backup.sh

권장 방법: 로그 파일로 리다이렉션

실무에서는 출력을 버리지 말고 로그 파일에 저장하는 것이 좋습니다:

bashMAILTO=""

# 로그 파일에 저장하되, 에러도 포함
0 2 * * * /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1

# 스크립트 내부에서 타임스탬프와 함께 로깅
0 2 * * * { echo "=== $(date) ==="; /usr/local/bin/backup.sh; } >> /var/log/backup.log 2>&1
트러블슈팅
오류

crontab -r을 -e 대신 잘못 입력했거나, 습관적으로 엔터를 눌러버린 경우입니다. 확인 없이 즉시 모든 crontab을 삭제하는 매우 위험한 명령입니다.

즉시 할 일: 패닉하지 말고 체계적으로 접근

bash# 현재 상태 확인 (이미 삭제됐는지)
crontab -l
# no crontab for username  ← 삭제된 상태

복구 방법 1: 백업 파일에서 복원

평소에 crontab을 백업해두었다면:

bash# 백업 파일에서 복원
crontab ~/crontab-backup-20260325.txt

# 또는 /tmp에 있는 경우
crontab /tmp/crontab-backup.txt

# 복원 확인
crontab -l

복구 방법 2: 시스템 메일에서 복원 단서 찾기

cron이 실행 결과를 메일로 보내는 설정이었다면:

bash# 시스템 메일에서 cron 관련 내용 확인
cat /var/mail/$(whoami) | grep -A5 "Cron"
mail

복구 방법 3: 로그에서 과거 실행 명령 재구성

bash# syslog에서 과거 cron 실행 기록 확인
grep CRON /var/log/syslog | grep CMD
grep CRON /var/log/syslog.1 | grep CMD    # 이전 로그 파일

# journald에서 확인
journalctl -u cron --since "7 days ago" | grep CMD
Mar 25 02:00:01 myserver CRON[1234]: (root) CMD (/usr/local/bin/db-backup.sh)
Mar 25 03:00:01 myserver CRON[1256]: (root) CMD (/usr/local/bin/log-cleanup.sh)

이 로그에서 어떤 스크립트가 언제 실행됐는지 파악해 crontab을 재구성합니다.

복구 방법 4: /etc/cron.d/ 와 /etc/crontab 확인

사용자 crontab이 삭제된 경우, 시스템 crontab은 영향을 받지 않습니다:

bash# 시스템 crontab은 삭제 안 됨
cat /etc/crontab
ls /etc/cron.d/

앞으로 예방하기

bash# 1. alias로 -r를 항상 -ri로 교체
echo "alias crontab='crontab -i'" >> ~/.bashrc
source ~/.bashrc

# 2. crontab 편집 전후 자동 백업 스크립트
# ~/.local/bin/safe-crontab.sh
#!/bin/bash
BACKUP_DIR="$HOME/.crontab-backups"
mkdir -p "$BACKUP_DIR"
crontab -l > "$BACKUP_DIR/crontab-$(date +%Y%m%d-%H%M%S).txt" 2>/dev/null || true
crontab "$@"

# 3. crontab을 Git으로 관리
mkdir -p ~/dotfiles/cron
crontab -l > ~/dotfiles/cron/crontab
cd ~/dotfiles && git add -A && git commit -m "crontab 업데이트"

핵심 교훈: crontab -e를 사용하기 전에 항상 crontab -l > ~/crontab-backup.txt로 백업을 만드세요. 이 한 줄이 몇 시간의 재구성 작업을 막을 수 있습니다.

— — —

고급 활용 팁

중복 실행 방지 (flock 활용)

작업 실행 시간이 다음 실행 시각을 넘어버릴 경우, 중복으로 실행되어 문제가 생길 수 있습니다. flock으로 이를 방지합니다.

bash# flock으로 중복 실행 방지
*/10 * * * * /usr/bin/flock -n /tmp/myapp.lock /usr/local/bin/myapp.sh

# -n: 락을 획득하지 못하면 즉시 포기 (non-blocking)
# -w 30: 최대 30초 대기 후 포기
*/10 * * * * /usr/bin/flock -w 30 /tmp/myapp.lock /usr/local/bin/myapp.sh

여러 명령을 한 줄에

bash# &&로 순차 실행 (앞이 성공해야 뒤가 실행)
0 2 * * * /usr/local/bin/prepare.sh && /usr/local/bin/backup.sh

# ;로 순차 실행 (앞의 성공 여부 상관없이)
0 2 * * * /usr/local/bin/step1.sh ; /usr/local/bin/step2.sh

# 여러 명령을 그룹으로
0 2 * * * { /usr/local/bin/step1.sh; /usr/local/bin/step2.sh; } >> /var/log/combined.log 2>&1

조건부 실행

bash# 파일이 존재할 때만 실행
0 * * * * [ -f /var/run/myapp.pid ] && /usr/local/bin/monitor.sh

# 특정 프로세스가 실행 중일 때만 실행
*/5 * * * * pgrep -x nginx > /dev/null && /usr/local/bin/nginx-stats.sh

환경별 crontab 관리

bash# 프로덕션 서버에서만 실행
ENVIRONMENT=$(cat /etc/environment-name 2>/dev/null || echo "dev")
0 2 * * * [ "$ENVIRONMENT" = "production" ] && /usr/local/bin/prod-only.sh

— — —

실습 과제

기본 실습

실습 1: 다음 조건의 crontab 항목을 작성하세요.

  • 매주 평일(월~금) 오전 8시 55분에 /usr/local/bin/morning-check.sh 실행
  • 실행 결과를 /var/log/morning-check.log에 append 저장

실습 2: 현재 시스템의 crontab 구성을 확인하세요.

bash# 다음 명령을 실행하고 결과를 분석하세요
crontab -l
cat /etc/crontab
ls /etc/cron.d/
ls /etc/cron.daily/

실습 3: 다음 crontab 표현식이 언제 실행되는지 설명하세요.

0 */6 * * *
30 9-17/2 * * 1-5
0 0 1,15 * *
@reboot
*/30 * * * 1,3,5

응용 실습

실습 4: 다음 시나리오의 crontab을 작성하고 테스트하세요.

  • 매 5분마다 /tmp/heartbeat.txt에 현재 시각을 기록하는 스크립트
  • 5분 후 파일이 생성됐는지 확인
bash# 힌트: 스크립트
#!/bin/bash
echo "$(date '+%Y-%m-%d %H:%M:%S') - alive" >> /tmp/heartbeat.txt

# crontab 등록 힌트
*/5 * * * * /usr/local/bin/heartbeat.sh

실습 5: systemd timer로 5분마다 헬스 체크를 실행하는 유닛 파일 두 개(.service, .timer)를 작성하고 활성화하세요.

— — —

요약

개념 핵심 내용
cron 데몬 매 분마다 깨어나 스케줄 확인, 자식 프로세스로 작업 실행
5-필드 문법 분 시 일 월 요일 — 순서를 외워두세요
특수 문자 *(모두), ,(목록), -(범위), /(간격)
@단축어 @reboot, @daily, @weekly 등
crontab -e 편집 (저장 시 자동 로드)
crontab -l 목록 확인
crontab -r 전체 삭제 (위험! 항상 -ri 사용)
/etc/crontab 시스템 전체, 사용자 필드 포함
/etc/cron.d/ 패키지별 cron 파일, 사용자 필드 포함
/etc/cron.daily/ 파일 배치만으로 매일 실행, 확장자 금지
PATH 문제 cron의 PATH는 제한적 — 절대 경로 또는 명시적 PATH 설정
MAILTO="" cron 메일 출력 비활성화
systemd timer 풍부한 로그, 의존성 지원, 놓친 실행 처리

cron은 단순하지만 강력합니다. PATH 문제만 주의하면 대부분의 자동화 작업을 안정적으로 처리할 수 있습니다. 복잡한 워크플로우나 상세한 모니터링이 필요하다면 systemd timer를 검토하세요.

다음 모듈에서는 logrotate와 journald를 활용해 서버 로그를 자동으로 압축·분할하고 디스크 풀 장애를 예방하는 방법을 다룹니다.

💡 더 많은 인프라 관련 실무 지식과 실습을 경험해보고 싶으신가요?

리눅스, 도커, 데이터베이스, 네트워킹 등 다양한 인프라 기술을 가상 환경에서 직접 실습하며 학습할 수 있습니다.
기술에 관심이 있고 한 단계 더 성장하고 싶다면, 인프라 학습 플랫폼에서 무료로 학습을 시작해 보세요!
👇 실습 플랫폼으로 이동하려면 아래 사진을 클릭해 주세요! 👇

인프라 학습 플랫폼 바로가기
반응형
LIST

'Linux' 카테고리의 다른 글

[Linux] BIOS/UEFI, GRUB 로더부터 systemd 시작까지의 부팅 프로세스 완전 분석  (0) 2026.06.14
[Linux] firewalld & iptables로 특정 IP/포트 차단 및 허용 규칙 설정  (0) 2026.06.14
[Linux] 서버 장애 초동 대응 — 5분 트리아지  (1) 2026.05.23
[Linux] SSH 고급 & 보안 하드닝  (0) 2026.05.22
[Linux] 포트 & 네트워크 진단  (0) 2026.05.22
'Linux' 카테고리의 다른 글
  • [Linux] BIOS/UEFI, GRUB 로더부터 systemd 시작까지의 부팅 프로세스 완전 분석
  • [Linux] firewalld & iptables로 특정 IP/포트 차단 및 허용 규칙 설정
  • [Linux] 서버 장애 초동 대응 — 5분 트리아지
  • [Linux] SSH 고급 & 보안 하드닝
cumo
cumo
  • cumo
    이것저것
    cumo
    • 분류 전체보기 (157)
      • 이것저것 (1)
      • 보안뉴스 (15)
      • Project (12)
      • wargame (1)
      • Cloud (25)
      • DevOps (21)
      • Linux (46)
      • 네트워크 (23)
      • AWS Developer BootCamp (1)
      • WEB&WAS (3)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

    • 도구모음 사이트
    • 참고 기술 블로그
  • 공지사항

  • 인기 글

  • 태그

    ubuntu
    서버 헬스체크 스크립트
    Linux
    서버편집
    bash 알림
    nano
    vi
    서비스 다운 감지
    bash 자동화
    텍스트편집기
    슬랙 알림 bash
    CIS 벤치마크
    자동 재시작 스크립트
    인프라
    vim
    데몬
    리눅스
    루트 로그인 비활성화
    curl 헬스체크
    서버관리
  • 최근 댓글

  • 최근 글

  • 반응형
  • hELLO· Designed By정상우.v4.10.3
cumo
[Linux] cron으로 리눅스 주기적 반복 작업(배치) 예약 및 백업 자동화
상단으로

티스토리툴바