Published on

리눅스 cron 미실행? PATH·메일로그·권한 점검

Authors

서버에서 crontab -e로 작업을 등록했는데도 “아무 일도 안 일어난다”는 상황은 흔합니다. 특히 운영 환경에서는 cron이 비대화형(non-interactive), 최소 환경 변수, 다른 사용자 컨텍스트에서 실행된다는 점 때문에 로컬에서 되던 스크립트가 실패하기 쉽습니다.

이 글은 “cron 미실행”을 빠르게 좁히기 위한 실전 체크리스트를 PATH, 메일/로그, 권한 중심으로 정리합니다. 마지막에는 재발 방지를 위한 템플릿도 제공합니다.

1) 먼저 확인: cron 데몬이 살아있는가

cron이 아예 실행되지 않는 경우는 생각보다 많습니다. 배포/이미지 교체, systemd 설정 변경, 컨테이너 환경 등에서 cron 데몬이 내려가 있을 수 있습니다.

systemd에서 상태 확인

sudo systemctl status cron
# 배포판에 따라 서비스명이 crond 일 수 있음
sudo systemctl status crond

sudo systemctl is-enabled cron
sudo systemctl start cron
  • Ubuntu/Debian 계열은 보통 cron
  • RHEL/CentOS/Amazon Linux 계열은 보통 crond

서비스가 재시작 루프에 빠졌다면 cron 자체 문제라기보다 데몬/유닛 구성 문제일 수 있습니다. systemd 쪽 진단 흐름은 아래 글도 함께 보시면 좋습니다.

cron이 읽는 파일이 맞는지

cron 등록 위치가 다르면 실행되지 않습니다.

  • 사용자 크론: crontab -e 로 편집되는 사용자별 크론
  • 시스템 크론: /etc/crontab, /etc/cron.d/*, /etc/cron.{hourly,daily,weekly,monthly}/*

특히 /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 /usr/local/bin/job.sh

반면 crontab -e는 사용자 필드가 없습니다.

# crontab -e 예시 (사용자 필드 없음)
*/5 * * * * /usr/local/bin/job.sh

2) 가장 흔한 원인: PATH와 환경변수 차이

cron은 로그인 셸이 아니기 때문에 ~/.bashrc, ~/.profile 등이 적용되지 않는 경우가 많습니다. 결과적으로 python, node, java, psql 같은 명령이 “대화형에서는 되는데 cron에서는 안 되는” 문제가 발생합니다.

증상

  • 스크립트 내부에서 python 실행이 실패
  • npm, nvm, pyenv 기반 실행이 실패
  • command not found가 나지만 로그를 안 남겨서 모름

해결 원칙 1: 실행 파일은 절대경로로

* * * * * /usr/bin/python3 /opt/app/scripts/run.py

명령 절대경로는 which 또는 command -v로 확인합니다.

command -v python3
command -v node
command -v psql

해결 원칙 2: cron에 PATH를 명시

crontab -e 상단에 PATH를 박아두면 재현성이 좋아집니다.

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

*/10 * * * * /opt/app/bin/nightly.sh

해결 원칙 3: 셸을 명시하고, 필요한 env는 파일로 로드

스크립트가 bash 문법(배열, [[ ]])을 쓰는데 기본 셸이 sh라서 깨지는 경우도 있습니다.

SHELL=/bin/bash

*/5 * * * * . /opt/app/.env && /opt/app/bin/job.sh

다만 . /opt/app/.env 는 해당 파일이 cron 실행 사용자에게 읽기 권한이 있어야 하며, .env 포맷이 KEY=VALUE 형태로 안전하게 구성돼 있어야 합니다.

3) “실행은 됐는데 실패”를 잡는 핵심: 메일과 로그

cron은 기본적으로 표준출력/표준에러를 메일로 보낼 수 있습니다. 하지만 서버가 메일 전송이 막혀 있거나, 로컬 MTA가 없으면 아무것도 안 남는 것처럼 보일 수 있습니다.

3-1) 가장 안전한 방법: 리다이렉션으로 로그 파일 남기기

*/5 * * * * /opt/app/bin/job.sh >> /var/log/job.log 2>&1
  • >> 로 append
  • 2>&1 로 에러도 함께 기록

로그 파일 권한 때문에 또 실패하는 경우가 있으니, 처음에는 사용자 홈 아래에 로그를 남기는 것도 방법입니다.

*/5 * * * * /opt/app/bin/job.sh >> /home/appuser/job.log 2>&1

3-2) MAILTO로 메일 수신자 지정

MAILTO=ops@example.com

0 2 * * * /opt/app/bin/nightly.sh

메일이 실제로 발송되려면 서버에 sendmail 또는 postfix 같은 MTA가 구성돼 있어야 합니다.

3-3) cron 로그 위치 확인 (배포판별 차이)

cron 로그는 환경에 따라 위치가 다릅니다.

  • Debian/Ubuntu: /var/log/syslog 에 cron 로그가 섞여 있는 경우가 많음
  • RHEL/CentOS 계열: /var/log/cron
  • systemd journal 사용: journalctl -u cron 또는 journalctl -u crond

예시 명령:

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

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

# systemd journal
sudo journalctl -u cron -n 200 --no-pager
sudo journalctl -u crond -n 200 --no-pager

로그에서 자주 보이는 단서:

  • CMD 라인이 아예 없다: 스케줄이 로딩되지 않았거나 시간 조건 불일치
  • CMD 는 있는데 결과가 없다: 출력이 메일로 갔거나, 내부에서 조용히 실패
  • Permission denied: 권한/소유자/실행비트 문제

4) 권한/소유자/실행비트: “파일은 있는데 실행이 안 됨”

cron은 등록된 사용자 권한으로 실행됩니다. 따라서 다음을 확인해야 합니다.

4-1) 스크립트 실행 비트

ls -l /opt/app/bin/job.sh
chmod +x /opt/app/bin/job.sh

4-2) shebang 확인

스크립트 첫 줄이 없거나 잘못되면 cron에서 실패할 수 있습니다.

head -n 1 /opt/app/bin/job.sh
# 예: #!/usr/bin/env bash

권장 예시:

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

4-3) 파일/디렉터리 접근 권한

실행 파일만 x가 있어도 안 됩니다. 경로 상위 디렉터리들에 x(traverse) 권한이 있어야 합니다.

namei -l /opt/app/bin/job.sh

namei -l 출력에서 중간 경로에 권한이 막힌 지점이 없는지 확인합니다.

4-4) root crontab과 사용자 crontab 혼동

sudo crontab -e 로 등록하면 root로 실행됩니다. 반대로 일반 사용자 crontab -e는 해당 사용자로 실행됩니다.

“권한 때문에 root로 돌리자”는 접근은 위험할 수 있습니다. 데이터 파일 권한, 보안, 사고 범위가 커집니다. 가능하면 필요한 권한만 부여하고 사용자 권한으로 실행하세요.

5) 시간/타임존/스케줄 문법: 의외로 자주 틀리는 부분

5-1) 서버 타임존 확인

date
cat /etc/timezone 2>/dev/null || true
timedatectl | sed -n '1,15p'

서버가 UTC인데 KST 기준으로 생각하고 “왜 새벽 2시에 안 돌지?” 같은 혼동이 생깁니다.

5-2) crontab 문법과 특수문자

  • % 는 cron에서 줄바꿈으로 해석될 수 있어 명령에 포함되면 깨질 수 있습니다. 필요하면 이스케이프 처리하거나 스크립트로 옮기세요.
  • 요일/일자 조건이 겹칠 때 기대와 다른 동작을 할 수 있습니다.

5-3) 최소 재현 스케줄로 테스트

문제 분리를 위해 1분마다 단순 로그를 찍어보면 “크론이 실행은 되는지”부터 확정할 수 있습니다.

* * * * * /bin/date >> /tmp/cron-smoke.log 2>&1

이 로그가 쌓이면 cron 자체는 정상이고, 원인은 스크립트/환경/권한 쪽으로 좁혀집니다.

6) 작업이 동시에 겹쳐서 “안 돈 것처럼 보이는” 케이스

배치가 5분마다 실행되는데 한 번 실행이 10분 걸리면, 다음 실행이 겹치며 락/중복 방지 로직 때문에 스킵될 수 있습니다.

flock으로 중복 실행 방지(권장)

*/5 * * * * /usr/bin/flock -n /tmp/job.lock /opt/app/bin/job.sh >> /var/log/job.log 2>&1
  • -n 은 락을 못 잡으면 즉시 종료
  • “왜 어떤 시간대는 실행이 안 됐지?”를 로그로 남기려면, 락 실패 시 별도 기록을 추가할 수도 있습니다.

7) 운영에서 추천하는 “안전한 cron 템플릿”

cron은 “조용히 실패”하기 쉬우니, 아래처럼 표준화하면 장애 대응이 훨씬 쉬워집니다.

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

# 1) 절대경로 실행
# 2) flock으로 중복 방지
# 3) stdout/stderr 로그 파일로 수집
# 4) 스크립트 내부에서 set -euo pipefail

*/10 * * * * /usr/bin/flock -n /tmp/nightly.lock /opt/app/bin/nightly.sh >> /var/log/nightly.log 2>&1

스크립트 예시:

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

cd /opt/app

/usr/bin/python3 /opt/app/scripts/do_work.py

8) 점검 순서 요약(현장용)

  1. systemctl status cron 또는 systemctl status crond
  2. * * * * * /bin/date 스모크 테스트로 “cron이 도는지” 확정
  3. cron 로그 확인: /var/log/syslog, /var/log/cron, journalctl -u cron
  4. PATH 문제 제거: 절대경로 사용, PATH= 명시
  5. 출력 수집: >> logfile 2>&1
  6. 권한 확인: 실행비트, shebang, namei -l로 경로 권한
  7. 겹침 방지: flock

서버 장애를 빠르게 좁히는 관점에서는 “증상을 로그로 고정시키는 것”이 가장 중요합니다. cron도 마찬가지로, 먼저 출력/에러가 어디로 가는지 통제하면 PATH든 권한이든 원인이 즉시 드러납니다.

추가로 파일 디스크립터 고갈 같은 리소스 이슈가 배치 실패로 이어지는 경우도 있습니다. 배치가 갑자기 죽거나 로그에 Too many open files 류가 보이면 아래 글의 체크리스트도 함께 확인해보세요.