Published on

리눅스 cron 미실행 원인 12가지 체크리스트

Authors

cron이 “분명 등록했는데” 실행되지 않을 때는 대개 두 부류로 나뉩니다.

  • 스케줄 자체가 트리거되지 않음: 데몬이 안 떠 있거나, crontab이 로드되지 않았거나, 시간/필드가 잘못됨
  • 트리거는 됐는데 작업이 실패: PATH/권한/환경/출력 처리 문제로 스크립트가 조용히 죽음

아래 체크리스트는 "트리거"부터 "실행 컨텍스트"까지 위에서 아래 순서로 보면 진단 시간이 확 줄어듭니다.

systemd 기반 서버에서 데몬이 재부팅 후 안 올라오는 문제는 cron에서도 흔합니다. 비슷한 접근으로 점검하려면 systemd 서비스가 재부팅 후 안 뜰 때 9분 진단도 함께 참고하세요.

0) 먼저 확인할 것: cron 로그가 어디에 찍히는가

배포판마다 cron 로그 위치가 다릅니다.

  • Debian/Ubuntu: 보통 journalctl 또는 /var/log/syslog
  • RHEL/CentOS/Amazon Linux: /var/log/cron

아래 명령으로 “실제로 트리거가 발생했는지”부터 확인하세요.

# systemd journal 기반
sudo journalctl -u cron -S "today" -n 200 --no-pager
sudo journalctl -u crond -S "today" -n 200 --no-pager

# 파일 로그 기반(배포판에 따라 존재)
sudo tail -n 200 /var/log/cron
sudo tail -n 200 /var/log/syslog

# 키워드로 필터
sudo grep -R "CRON" -n /var/log 2>/dev/null | tail -n 50

로그에 "CMD" 라인이 보이면 트리거는 되었고, 이후는 스크립트/환경 문제일 가능성이 큽니다.

1) cron 데몬이 실행 중인가 (가장 흔함)

cron 서비스 이름은 cron 또는 crond 입니다.

sudo systemctl status cron
sudo systemctl status crond

# 부팅 시 자동 시작 여부
sudo systemctl is-enabled cron
sudo systemctl is-enabled crond

# 실행 안 되어 있으면
sudo systemctl enable --now cron
# 또는
sudo systemctl enable --now crond

컨테이너나 최소 설치 이미지에서는 cron 패키지 자체가 없거나 데몬이 기본 비활성인 경우도 많습니다.

2) “어느 crontab”에 등록했는지 착각 (root vs 사용자)

사용자별 crontab은 서로 완전히 분리됩니다.

# 현재 사용자 crontab
crontab -l

# root crontab
sudo crontab -l

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

root로 실행돼야 하는 작업을 일반 사용자 crontab에 넣어두면 권한 문제로 실패하거나, 반대로 사용자 홈/환경을 기대하는 작업을 root에 넣으면 경로가 달라져 실패할 수 있습니다.

3) crontab 문법 오류 또는 개행/인코딩 문제

crontab은 문법이 조금만 틀려도 해당 라인이 무시됩니다.

  • 필드 개수 부족
  • * /5 같이 공백이 들어간 잘못된 표현
  • Windows 개행(CRLF)이 섞인 스크립트

진단 팁:

# 편집은 이걸로(문법 체크에 유리)
crontab -e

# 스크립트가 CRLF인지 확인
file /path/to/job.sh

# CRLF면 변환
sed -i 's/\r$//' /path/to/job.sh

또한 @reboot 같은 특수 문자열은 구현에 따라 동작이 미묘하게 다를 수 있으니, 재부팅 트리거가 필요하면 systemd 타이머로 전환도 고려하세요.

4) 시간 필드 착각: cron은 “로컬 타임존”을 따른다

서버가 UTC인데 KST 기준으로 생각하면 “안 돈다”로 보입니다.

date
timedatectl

# 타임존 변경(운영 정책에 맞게)
sudo timedatectl set-timezone Asia/Seoul

또한 day-of-monthday-of-week 를 동시에 지정했을 때 동작은 구현/문서에 따라 혼동이 많습니다. 일반적으로 둘 중 하나만 지정하는 방식으로 단순화하세요.

5) PATH/환경변수 없음: cron은 매우 최소 환경으로 실행된다

터미널에서는 되는데 cron에서는 실패하는 1순위입니다.

  • PATH가 짧아서 python, node, aws, kubectl 등을 못 찾음
  • HOME, LANG, JAVA_HOME, NVM_DIR 같은 환경이 없음

해결은 “절대경로 사용” 또는 “crontab에서 PATH 지정”입니다.

# crontab 상단에 명시
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

* * * * * /usr/bin/python3 /opt/jobs/hello.py

환경이 더 복잡하면 스크립트에서 필요한 환경 파일을 명시적으로 로드하세요.

#!/usr/bin/env bash
set -euo pipefail

# 예: 시스템 공용 환경 또는 앱 환경 로드
[ -f /etc/profile ] && . /etc/profile
[ -f /opt/app/.env ] && . /opt/app/.env

/usr/local/bin/node /opt/app/job.js

6) 실행 권한/소유권/마운트 옵션 문제

스크립트에 실행 권한이 없거나, 디렉터리 권한이 막혀 있거나, noexec 마운트면 실행이 안 됩니다.

ls -l /path/to/job.sh
chmod +x /path/to/job.sh

# 상위 디렉터리 접근 권한도 확인
namei -l /path/to/job.sh

# noexec 확인
mount | grep -E " /opt | /home | /var "

권한 문제는 로그에 남지 않고 조용히 실패하는 경우가 있어, 아래 “출력 리다이렉션”도 함께 적용하는 게 좋습니다.

7) 작업이 실행되지만 출력이 메일로 보내지다 실패(또는 메일 없음)

cron은 기본적으로 stdout/stderr를 메일로 보내려 합니다. 서버에 MTA가 없으면 메시지가 유실되거나 지연될 수 있습니다.

가장 안전한 패턴은 로그 파일로 리다이렉션입니다.

* * * * * /opt/jobs/job.sh >> /var/log/job.log 2>&1

또는 실행 실패 시에만 알림을 받고 싶다면, 스크립트에서 종료코드 기반으로 별도 알림을 넣는 편이 운영에 유리합니다.

8) 스크립트의 shebang 문제 또는 /bin/sh 호환성

crontab은 기본적으로 /bin/sh로 실행되는 경우가 많습니다. 로컬에서는 bash로 돌려서 되던 문법이 cron에서는 깨질 수 있습니다.

  • [[ ... ]], 배열, source 등은 sh에서 동작이 다를 수 있음

해결:

  • 스크립트 첫 줄에 정확한 shebang
  • crontab에 SHELL=/bin/bash 지정
#!/usr/bin/env bash
# bash 문법 사용 가능

9) 상대경로 사용: cron의 작업 디렉터리는 기대와 다르다

cron에서 현재 디렉터리는 보통 사용자 홈이거나 /일 수 있습니다. 상대경로로 파일을 열면 실패합니다.

해결:

  • 스크립트 시작 시 cd로 고정
  • 파일 경로는 전부 절대경로
#!/usr/bin/env bash
set -euo pipefail
cd /opt/app

./bin/run-task --config /opt/app/config/prod.yml

10) cron.allow / cron.deny 또는 PAM 정책으로 사용자 차단

특정 사용자가 cron 사용이 제한될 수 있습니다.

# 존재 여부 확인
ls -l /etc/cron.allow /etc/cron.deny 2>/dev/null

# 내용 확인
sudo cat /etc/cron.allow 2>/dev/null
sudo cat /etc/cron.deny 2>/dev/null

cron.allow가 존재하면 거기에 있는 사용자만 허용되는 정책인 경우가 많습니다.

11) /etc/cron.d 또는 /etc/crontab 형식 착각 (사용자 필드 필요)

crontab -e로 편집하는 사용자 crontab은

  • 분 시 일 월 요일 명령

형식입니다.

반면 /etc/crontab/etc/cron.d/*

  • 분 시 일 월 요일 사용자 명령

처럼 사용자 필드가 추가됩니다. 이걸 빼먹으면 실행이 안 됩니다.

예시:

# /etc/cron.d/myjob 예시 (사용자 필드 포함)
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

*/5 * * * * root /opt/jobs/job.sh >> /var/log/job.log 2>&1

또한 /etc/cron.d 파일은 권한/소유권이 비정상이면 무시될 수 있으니 다음도 확인하세요.

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

12) 중복 실행 방지(락) 또는 이전 실행이 “끝나지 않음”

“안 돈다”가 아니라 실제로는 이전 실행이 살아있어서 다음 실행이 스킵되는 경우가 있습니다.

  • 스크립트 내부에서 락 파일을 잡고 해제하지 못함
  • flock을 쓰는데 장시간 작업으로 다음 주기가 대기
  • 외부 명령이 대기 상태로 걸림(예: 네트워크, DNS)

점검:

# 동일 작업이 떠 있는지 확인
ps aux | grep -E "job.sh|run-task" | grep -v grep

# 락 파일 확인(예시)
ls -l /var/lock/myjob.lock 2>/dev/null

권장 패턴(중복 실행 방지):

* * * * * flock -n /var/lock/myjob.lock /opt/jobs/job.sh >> /var/log/job.log 2>&1

네트워크/DNS 문제로 외부 호출이 멈추는 경우도 많습니다. 쿠버네티스/클라우드 환경에서 통신 이슈를 다루는 방식은 EKS에서 IPv6만 통신 실패 - CNI·SG·DNS 점검에서의 체크 포인트도 참고할 만합니다.

재현 가능한 “최소 크론 테스트”로 트리거부터 분리하기

원인 파악이 어려우면, 복잡한 스크립트를 잠시 치우고 가장 단순한 작업으로 트리거 여부를 분리하세요.

* * * * * date >> /tmp/cron-smoke-test.log 2>&1
  • 이 로그가 쌓이면 cron 트리거는 정상
  • 안 쌓이면 1번(데몬), 2번(등록 위치), 4번(시간), 10번(허용 정책)부터 다시 확인

운영에서 추천하는 크론 작성 템플릿

실패를 숨기지 않도록 “환경 고정 + 절대경로 + 로그”를 기본값으로 두면 장애가 크게 줄어듭니다.

SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

# 매 10분 실행, 중복 실행 방지, 로그 남김
*/10 * * * * flock -n /var/lock/app-job.lock /opt/app/bin/job.sh >> /var/log/app-job.log 2>&1

마무리: 진단 순서를 고정하면 해결이 빨라진다

정리하면, cron 미실행은 대부분 다음 순서로 좁혀집니다.

  1. 로그에서 트리거가 있었는지 확인
  2. 데몬 상태 확인
  3. crontab 위치/형식 확인
  4. 시간대/스케줄 표현 확인
  5. PATH/권한/작업 디렉터리/셸 차이로 실행 실패 확인
  6. 출력 리다이렉션으로 “조용한 실패”를 가시화

이 순서대로 체크하면, 감으로 찍는 디버깅이 아니라 재현 가능한 방식으로 원인을 제거할 수 있습니다.