Published on

리눅스 crontab 안 돌아갈 때 필수 점검 9가지

Authors

서버에서 crontab -e로 등록까지 해놨는데 작업이 안 돌아가면, 원인은 의외로 단순한 곳에 숨어 있는 경우가 많습니다. 크론은 “터미널에서 실행되던 것”을 “거의 아무 환경도 없는 상태”에서 실행합니다. 그래서 쉘 환경, PATH, 권한, 데몬 상태, 시간대, 출력 처리 같은 기본기를 한 번에 점검하는 체크리스트가 가장 빠릅니다.

아래 9가지는 운영에서 실제로 가장 많이 맞닥뜨리는 원인 순으로 정리했습니다. 각 항목은 “증상 → 확인 명령 → 해결” 흐름으로 따라가면 됩니다.

1) 크론 데몬이 실행 중인지 (cron vs crond)

가장 먼저 “스케줄러 자체가 살아 있나”를 봐야 합니다. 배포/재부팅 이후 서비스가 비활성화되어 있거나, 컨테이너 환경에서 아예 실행되지 않는 경우가 있습니다.

확인

# Debian/Ubuntu
sudo systemctl status cron

# RHEL/CentOS/Amazon Linux
sudo systemctl status crond

# 프로세스 확인
ps -ef | grep -E 'cron|crond' | grep -v grep

해결

# Debian/Ubuntu
sudo systemctl enable --now cron

# RHEL/CentOS/Amazon Linux
sudo systemctl enable --now crond

컨테이너라면 “컨테이너 안에서 cron을 돌릴지” 혹은 “호스트/쿠버네티스 CronJob으로 옮길지”부터 설계를 재검토하는 게 안전합니다.

2) crontab을 ‘어느 사용자’에 등록했는지

크론은 사용자별로 분리됩니다. root로 등록했는데 실제로는 일반 사용자 크론을 확인하고 있거나(반대도 동일), sudo crontab -ecrontab -e를 혼동하는 케이스가 매우 흔합니다.

확인

# 현재 사용자 크론
crontab -l

# root 크론
sudo crontab -l

# 특정 사용자 크론
sudo crontab -u someuser -l

해결 팁

  • 시스템 작업(백업, 로그 로테이션, 서비스 재시작)은 root 크론이 일반적입니다.
  • 애플리케이션 배치(ETL, 리포트 생성)는 애플리케이션 실행 사용자로 고정하는 편이 권한/감사 측면에서 좋습니다.

3) 스케줄 문법/개행/CRLF 문제

문법 오류가 있으면 크론이 해당 라인을 무시하거나, 전체 파일 로딩이 꼬일 수 있습니다. 특히 윈도우에서 편집해 CRLF가 섞이면 예상치 못한 동작이 납니다.

확인

# 크론 파일 위치는 배포판마다 다를 수 있으나, 사용자 크론은 보통 spool에 저장됩니다.
# CRLF 여부 확인(파일 경로는 예시)
sudo file /var/spool/cron/crontabs/$USER 2>/dev/null || true

# 문법 빠른 검증: 최소한의 테스트 라인 추가
# 매분 실행 테스트(로그 파일에 찍기)

해결

  • 편집기는 가능하면 서버에서 crontab -e로 직접 편집합니다.
  • CRLF가 의심되면 dos2unix로 변환합니다.
sudo dos2unix /var/spool/cron/crontabs/$USER

4) PATH와 환경변수: “터미널에서는 되는데 크론에서는 안 됨” 1순위

크론은 로그인 쉘이 아니므로 PATH가 매우 짧고, pyenv, nvm, conda, asdf 등은 거의 로딩되지 않습니다. 그래서 python, node, psql, aws 같은 명령이 크론에서만 실패합니다.

확인: 크론 환경 덤프

크론이 실제 어떤 환경에서 실행되는지 파일로 남겨 보면 바로 보입니다.

# crontab에 임시로 추가
* * * * * env | sort > /tmp/cron-env.$USER.txt

해결: 명령은 절대경로로, 필요한 환경은 명시

# 예: PATH를 명시하고, 실행 파일은 절대경로로
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

* * * * * /usr/bin/python3 /opt/app/jobs/daily.py >> /var/log/app/daily.log 2>&1

bash -lc로 로그인 쉘을 흉내 내는 방법도 있지만, 의존성이 늘어 운영이 불안해질 수 있습니다.

* * * * * /bin/bash -lc 'cd /opt/app && ./run_job.sh' >> /var/log/app/job.log 2>&1

5) 실행 권한/소유자/umask 문제

스크립트 파일에 실행 권한이 없거나, 크론 실행 사용자에게 읽기 권한이 없으면 조용히 실패합니다. 또한 크론의 기본 umask 때문에 생성 파일 권한이 달라져 후속 작업이 깨지기도 합니다.

확인

ls -l /opt/app/jobs/run.sh
namei -l /opt/app/jobs/run.sh

해결

# 실행 권한 부여
chmod +x /opt/app/jobs/run.sh

# 크론에서 명시적으로 umask 지정(필요한 경우)
* * * * * umask 022; /opt/app/jobs/run.sh >> /var/log/app/run.log 2>&1

6) 현재 디렉터리(CWD)와 상대경로 의존

크론은 기본 작업 디렉터리가 사용자의 홈이거나 /인 경우가 많습니다. 스크립트에서 ./config.yml, logs/app.log 같은 상대경로를 쓰면 크론에서만 파일을 못 찾습니다.

해결: cd를 명시하거나 절대경로 사용

# cd를 명시
* * * * * cd /opt/app && ./jobs/run.sh >> /var/log/app/run.log 2>&1

# 또는 스크립트 내부에서 기준 디렉터리 고정
# bash 예시
# BASE_DIR=$(cd "$(dirname "$0")" && pwd)

7) 출력/에러 처리: 로그가 없어서 “안 돈다”로 보이는 경우

크론은 기본적으로 표준출력/표준에러를 메일로 보내려 합니다. 서버에 MTA가 없거나 메일이 버려지면, 실패 흔적이 사라집니다. 그러면 실제로는 실행되었는데 “로그가 없어서 안 돈다”로 오해합니다.

권장 패턴: 항상 로그로 리다이렉션

# stdout, stderr 모두 파일로
* * * * * /opt/app/jobs/run.sh >> /var/log/app/run.log 2>&1

시스템 로그에서 크론 실행 흔적 찾기

# Debian/Ubuntu
sudo grep -i cron /var/log/syslog | tail -n 200

# RHEL 계열
sudo grep -i cron /var/log/cron | tail -n 200

# systemd journal
sudo journalctl -u cron -n 200 --no-pager 2>/dev/null || true
sudo journalctl -u crond -n 200 --no-pager 2>/dev/null || true

로그가 없으면 원인 파악이 어려워집니다. 운영 배치는 “실행 시작/종료/실패”를 남기도록 스크립트 자체에 로깅을 넣는 것도 추천합니다.

8) 시간대/서머타임/서버 시간 불일치

“매일 00:10에 도는데 오늘만 안 돎” 같은 이슈는 시간대가 바뀌었거나(NTP, 이미지 변경), DST 영향으로 특정 시간이 스킵/중복되었을 수 있습니다.

확인

date
timedatectl

# NTP 동기화 상태
timedatectl timesync-status 2>/dev/null || true
chronyc tracking 2>/dev/null || true

해결

  • 서버 표준 시간대를 고정(보통 UTC)하고, 애플리케이션에서 표시만 로컬 타임존으로 변환하는 전략이 안전합니다.
  • 크론 라인에 CRON_TZ를 쓰는 방법도 있으나, 배포판/버전에 따라 제약이 있으니 운영 표준을 정해두는 게 좋습니다.

9) /etc/cron.* 및 /etc/crontab 사용 시 형식/권한/보안 정책(SELinux) 점검

사용자 크론(crontab -e)이 아니라 /etc/crontab, /etc/cron.d/*, /etc/cron.daily/* 등을 쓰는 환경에서는 추가 함정이 있습니다.

자주 하는 실수 1: /etc/crontab은 “user” 필드가 필요

사용자 크론과 달리 /etc/crontab/etc/cron.d/*는 실행 사용자 필드가 한 칸 더 들어갑니다.

# /etc/crontab 예시
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

# 분 시 일 월 요일 사용자 명령
*/5 * * * * root /opt/app/jobs/run.sh >> /var/log/app/run.log 2>&1

자주 하는 실수 2: /etc/cron.d/* 파일 권한/소유자

배포판에 따라 권한이 느슨하면 무시됩니다.

sudo ls -l /etc/cron.d
sudo chmod 644 /etc/cron.d/myjob
sudo chown root:root /etc/cron.d/myjob

자주 하는 실수 3: SELinux로 인해 실행/접근이 차단

특히 RHEL 계열에서 스크립트가 특정 경로에 있을 때 컨텍스트 때문에 막힐 수 있습니다.

# SELinux 상태
getenforce

# 거부 로그 확인(환경에 따라 경로 상이)
sudo ausearch -m avc -ts recent 2>/dev/null | tail -n 50 || true

SELinux가 원인이라면 “끄기”보다 정책/컨텍스트를 맞추는 방향이 바람직합니다.

빠른 재현용 체크 스크립트(권장)

아래처럼 크론에서 실행되는 최소 스크립트를 하나 만들어두면, 문제가 “크론 자체”인지 “내 작업”인지 즉시 분리할 수 있습니다.

# /opt/app/jobs/cron_smoke_test.sh
#!/usr/bin/env bash
set -euo pipefail

echo "[$(date -Is)] cron smoke test user=$(id -un) uid=$(id -u) pwd=$(pwd)" \
  >> /var/log/app/cron_smoke_test.log

env | sort >> /var/log/app/cron_smoke_test.log
echo "----" >> /var/log/app/cron_smoke_test.log
# crontab -e
* * * * * /opt/app/jobs/cron_smoke_test.sh

이 로그가 찍히면 크론은 정상이고, 이후부터는 작업 스크립트의 PATH/권한/의존성 문제로 범위를 좁히면 됩니다.

운영에서 자주 같이 터지는 이슈: 네트워크/권한/외부 의존성

크론 작업은 종종 DB, 외부 API, 내부 서비스에 의존합니다. “크론이 안 돈다”가 아니라 “크론은 돌았는데 네트워크/인증 문제로 실패”인 경우도 많습니다. 예를 들어 클러스터 네트워크나 보안 정책이 바뀌면 특정 프로토콜만 실패하기도 합니다. 이런 류의 트러블슈팅 감각은 아래 글도 함께 참고하면 도움이 됩니다.

마무리: “안 돈다”를 “어디서 끊겼다”로 바꾸기

크론 장애는 감으로 고치면 오래 걸립니다. 위 9가지를 순서대로 보면 대부분은 다음 중 하나로 귀결됩니다.

  • 데몬/사용자/문법 같은 “등록 자체” 문제
  • PATH, CWD, 권한 같은 “실행 환경” 문제
  • 로그 부재로 인한 “관측 불가” 문제

가장 추천하는 습관은 두 가지입니다. (1) 크론 라인에서 로그 리다이렉션을 표준화하고, (2) 명령은 절대경로와 cd를 명시하는 것입니다. 이 두 가지만 지켜도 “crontab 안 돌아감”의 80%는 사라집니다.