- Published on
리눅스 cron 미실행 원인 12가지 체크리스트
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
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-month 와 day-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 미실행은 대부분 다음 순서로 좁혀집니다.
- 로그에서 트리거가 있었는지 확인
- 데몬 상태 확인
- crontab 위치/형식 확인
- 시간대/스케줄 표현 확인
- PATH/권한/작업 디렉터리/셸 차이로 실행 실패 확인
- 출력 리다이렉션으로 “조용한 실패”를 가시화
이 순서대로 체크하면, 감으로 찍는 디버깅이 아니라 재현 가능한 방식으로 원인을 제거할 수 있습니다.