흩어진 크론탭을 한 파일로 — 홈서버 스케줄 중앙 관리

흩어진 크론 설정을 git으로 관리되는 단일 crontab.txt와 적용 스크립트로 중앙화하는 구조 비교

홈서버 운영이 자리를 잡으면 cron 작업이 하나둘 늘어난다. 백업, DDNS 갱신, 디스크 점검, 인증서 확인… 처음엔 crontab -e로 그때그때 추가한다. 그리고 1년 뒤, 이런 질문에 답할 수 없게 된다. “이 작업 아직도 필요한 건가? 언제 누가(과거의 내가) 왜 넣었지?”

crontab -e의 문제는 기능이 아니라 기록이다. 변경 이력이 없고, 백업도 따로 해야 하고, 작업과 그 작업의 로그가 어디 있는지를 잇는 정보도 없다. 서버를 재설치하면 크론탭은 가장 먼저 잊히는 복구 항목이 된다.

해법: 파일이 원본, crontab은 사본

구조는 단순하다. 스케줄을 정의하는 단일 텍스트 파일을 git 저장소 안에 두고, crontab에는 그 파일을 “적용”만 한다.

~/scheduler/
├── crontab.txt      # 모든 스케줄의 단일 원본
├── apply.sh         # crontab.txt → crontab 반영
└── logs/            # 작업별 로그 일원화

crontab.txt는 평범한 crontab 형식이되, 주석으로 각 작업의 목적을 기록한다.

# ── 백업: 일별 스냅샷 (보관 30일) ──
30 3 * * * /home/user/scheduler/jobs/snapshot-backup.sh >> /home/user/scheduler/logs/backup.log 2>&1

# ── DDNS 갱신 (5분 주기) ──
*/5 * * * * /home/user/scheduler/jobs/ddns-update.sh >> /home/user/scheduler/logs/ddns.log 2>&1

apply.sh는 두 줄짜리다.

#!/bin/bash
crontab /home/user/scheduler/crontab.txt
crontab -l | head -3   # 적용 결과 눈으로 확인

crontab 파일명 명령은 현재 crontab 전체를 그 파일 내용으로 교체한다 (crontab(5) 매뉴얼). 이 “교체” 동작이 핵심 규율을 만든다 — crontab -e로 손수정한 내용은 다음 apply 때 사라지므로, 모든 변경은 반드시 파일을 거치게 된다.

이 구조가 바꾸는 것들

  • 이력: 파일이 git 안에 있으니 “이 작업이 언제 왜 추가됐는지”가 커밋 로그에 남는다.
  • 복구: 서버 재설치 시 저장소 클론 + apply.sh 한 번으로 모든 스케줄이 돌아온다.
  • 로그 동선: 모든 작업이 logs/ 아래로 출력을 모으므로, 새벽에 뭔가 실패했을 때 어디를 봐야 할지 고민이 없다.
  • 점검 가능성: 파일 하나만 훑으면 이 서버의 자동화 전체가 보인다. 운영 인수인계(미래의 나에게)가 문서 없이 된다.

현재 내 crontab.txt에는 25개의 작업이 등록되어 있다. 백업, 5분 주기 서버 모니터링, 여러 자동화 프로젝트의 스케줄이 전부 이 파일 하나에 모여 있고, 그룹별 로그가 logs/ 아래로 떨어진다. 이 구조에서 가장 체감되는 효용은 새 자동화를 추가할 때다 — “크론을 어디에 어떻게 걸지”를 고민할 필요 없이 파일에 한 줄 쓰고 apply.sh 한 번이면 끝이라, 자동화 하나를 더하는 심리적 비용이 거의 0이 됐다. 작업이 25개쯤 되면 흩어진 관리로는 어차피 감당이 안 된다는 걸, 거꾸로 말하면 중앙화 덕에 25개까지 늘릴 수 있었다는 걸 운영하면서 실감한다.

크론의 두 가지 고전적 함정

중앙화와 별개로, 크론에서 모두가 한 번씩 밟는 함정 둘을 짚어둔다.

첫째, 크론의 환경은 내 셸과 다르다. 터미널에서는 잘 돌던 스크립트가 크론에서만 실패하는 단골 원인이다. 크론은 로그인 셸의 .bashrc를 읽지 않으므로 PATH가 최소한으로 잡혀 있고, 기본 셸도 bash가 아니라 sh인 경우가 많다. 해법은 습관으로 만든다 — 스크립트 안에서 모든 명령을 절대 경로로 쓰거나 스크립트 상단에서 PATH를 직접 지정하고, crontab.txt 맨 위에 SHELL=/bin/bash를 명시한다.

둘째, 작업이 겹쳐 도는 사고. 백업이 평소보다 오래 걸리는 날, 다음 주기의 같은 작업이 또 시작되면 두 프로세스가 같은 파일을 두고 충돌한다. flock으로 잠금을 걸면 한 줄로 예방된다.

30 3 * * * flock -n /tmp/backup.lock /home/user/scheduler/jobs/snapshot-backup.sh >> ... 2>&1

-n 옵션은 “이미 잠겨 있으면 기다리지 말고 그냥 종료”라는 뜻이다. 오래 걸릴 수 있는 모든 작업에 기본으로 붙여둘 가치가 있다.

운영 팁 세 가지

  1. 출력은 반드시 리다이렉트한다. >> 로그파일 2>&1이 없는 크론 작업의 출력은 로컬 메일로 가거나 버려진다. 침묵하는 실패가 가장 위험하다 — 실패를 능동적으로 알리는 방법은 텔레그램 알림 글에서 잇는다.
  2. 표현식은 검증하고 넣는다. */5 2 * * *가 정확히 언제 도는지 헷갈린다면 crontab.guru에서 확인하는 것이 빠르다. 시간대는 서버 기준임을 잊지 말 것 (timedatectl로 확인 — 초기 설정 글 5단계).
  3. 작업 스크립트도 같은 저장소에 둔다. 스케줄(언제)과 스크립트(무엇을)가 한 곳에 있어야 함께 버전관리된다. 위 구조의 jobs/ 폴더가 그 자리다.

마지막으로 시스템 크론과의 역할 구분도 정해두면 깔끔하다. /etc/cron.d//etc/cron.daily/는 패키지가 설치한 시스템 작업의 영역으로 남겨두고, 내가 만든 작업은 전부 사용자 crontab(즉 crontab.txt) 한 곳으로 모은다. “내 자동화는 여기만 보면 된다”는 단일 진입점이 흐려지는 순간 중앙화의 이점이 새기 시작하기 때문이다.

크론은 홈서버 자동화의 뼈대다. 뼈대가 한 파일로 정리되어 있으면, 그 위에 백업·모니터링·갱신 작업을 얹는 일이 두렵지 않게 된다. 반대로 뼈대가 흩어져 있으면 모든 자동화가 “건드리기 무서운 것”이 된다. 한 시간 투자로 옮겨둘 가치가 충분하다.