홈서버 운영이 자리를 잡으면 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 옵션은 “이미 잠겨 있으면 기다리지 말고 그냥 종료”라는 뜻이다. 오래 걸릴 수 있는 모든 작업에 기본으로 붙여둘 가치가 있다.
운영 팁 세 가지
- 출력은 반드시 리다이렉트한다.
>> 로그파일 2>&1이 없는 크론 작업의 출력은 로컬 메일로 가거나 버려진다. 침묵하는 실패가 가장 위험하다 — 실패를 능동적으로 알리는 방법은 텔레그램 알림 글에서 잇는다. - 표현식은 검증하고 넣는다.
*/5 2 * * *가 정확히 언제 도는지 헷갈린다면 crontab.guru에서 확인하는 것이 빠르다. 시간대는 서버 기준임을 잊지 말 것 (timedatectl로 확인 — 초기 설정 글 5단계). - 작업 스크립트도 같은 저장소에 둔다. 스케줄(언제)과 스크립트(무엇을)가 한 곳에 있어야 함께 버전관리된다. 위 구조의
jobs/폴더가 그 자리다.
마지막으로 시스템 크론과의 역할 구분도 정해두면 깔끔하다. /etc/cron.d/나 /etc/cron.daily/는 패키지가 설치한 시스템 작업의 영역으로 남겨두고, 내가 만든 작업은 전부 사용자 crontab(즉 crontab.txt) 한 곳으로 모은다. “내 자동화는 여기만 보면 된다”는 단일 진입점이 흐려지는 순간 중앙화의 이점이 새기 시작하기 때문이다.
크론은 홈서버 자동화의 뼈대다. 뼈대가 한 파일로 정리되어 있으면, 그 위에 백업·모니터링·갱신 작업을 얹는 일이 두렵지 않게 된다. 반대로 뼈대가 흩어져 있으면 모든 자동화가 “건드리기 무서운 것”이 된다. 한 시간 투자로 옮겨둘 가치가 충분하다.