- Published on
리눅스 crontab 안 돈다? 로그·환경·쉘 차이
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서버에서 crontab 이 “분명 등록했는데 안 돈다”는 상황은 대개 세 가지 축으로 수렴합니다.
- 로그가 없어서 실패 지점을 모른다
- 인터랙티브 셸과 크론 환경이 다르다
- 실행 셸(
/bin/sh) 차이로 스크립트가 깨진다
이 글은 위 3가지를 한 번에 끝내는 진단 체크리스트와, 운영에서 재발을 막는 템플릿까지 제공합니다.
1) 크론은 실행됐는데 “조용히” 실패한다
크론은 기본적으로 표준 출력/표준 에러를 메일로 보내거나(설정에 따라), 아무 데도 남기지 않고 끝나 버립니다. 그래서 첫 단계는 반드시 로그를 남기는 것입니다.
1-1. 가장 먼저 해야 할 것: 리다이렉션으로 로그 고정
크론 엔트리에 아래처럼 로그를 붙입니다.
* * * * * /usr/local/bin/myjob.sh >> /var/log/myjob.log 2>&1
>>는 append(누적)2>&1는 표준 에러도 같은 파일로
로그가 커지는 게 걱정이면 logrotate 를 붙이거나, 날짜별 파일로 분리합니다.
* * * * * /usr/local/bin/myjob.sh >> /var/log/myjob-$(date +\%F).log 2>&1
주의: 크론에서 % 는 줄바꿈으로 해석되므로 \%F 처럼 이스케이프가 필요합니다.
1-2. 시스템 로그에서 크론 실행 여부 확인
배포판마다 다르지만 보통 아래 중 하나에 기록됩니다.
# Debian/Ubuntu 계열
sudo grep CRON /var/log/syslog | tail -n 50
# RHEL/CentOS/Amazon Linux 계열
sudo tail -n 50 /var/log/cron
# systemd-journald 사용 시
sudo journalctl -u cron -n 100 --no-pager
sudo journalctl -u crond -n 100 --no-pager
여기서 확인할 포인트는 두 가지입니다.
- 스케줄 시각에 크론이 실제로 트리거 되었는가
- 트리거 되었는데도 스크립트가 내부에서 실패했는가
트리거 자체가 없다면 cron/crond 서비스 상태부터 봐야 합니다.
sudo systemctl status cron
sudo systemctl status crond
2) “내 터미널에서는 되는데 크론에서는 안 된다”의 정체: 환경변수
크론은 로그인 셸이 아닙니다. 따라서 터미널에서 자동으로 잡히던 환경이 거의 없습니다.
2-1. PATH가 다르다: 명령을 못 찾는다
가장 흔한 실패는 PATH 차이입니다. 예를 들어 터미널에서는 python, node, aws 가 되는데 크론에서는 command not found 가 납니다.
크론에서 PATH 를 고정하는 가장 단순한 방법은 crontab 상단에 명시하는 겁니다.
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
* * * * * myjob.sh >> /var/log/myjob.log 2>&1
또는 스크립트에서 실행 파일을 절대 경로로 호출하세요.
/usr/bin/python3 /opt/app/job.py
절대 경로는 다음으로 확인합니다.
command -v python3
command -v node
command -v aws
2-2. HOME, USER, LANG, TZ도 다르다
크론은 HOME 이 기대와 다르거나, LANG 이 C 로 잡혀 인코딩 문제가 터질 수 있습니다. 특히 CSV/한글 로그/외부 파일을 다루는 작업에서 자주 재현됩니다.
아래처럼 크론 상단에 최소 환경을 고정해두면 디버깅이 쉬워집니다.
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOME=/home/ubuntu
LANG=ko_KR.UTF-8
TZ=Asia/Seoul
* * * * * /usr/local/bin/myjob.sh >> /var/log/myjob.log 2>&1
인코딩 이슈가 의심되면 관련 글도 함께 참고하세요.
2-3. .bashrc/.profile 을 크론이 읽지 않는다
터미널에서 잘 되는 이유가 ~/.bashrc 에 있는 export 나 conda activate, nvm use 같은 초기화 때문이라면, 크론에서는 그대로 실패합니다.
해결책은 두 가지 중 하나입니다.
- 크론에 필요한 환경을 직접 정의
- 스크립트에서 필요한 초기화만 명시적으로
source
예시:
#!/usr/bin/env bash
set -euo pipefail
# 필요한 것만 명시적으로 로드
source /etc/profile
source "$HOME/.bashrc"
# 이후 작업
python3 /opt/app/job.py
다만 .bashrc 는 인터랙티브 전용 설정이 섞여 있을 수 있어, 운영 스크립트에서는 가급적 “작업 전용 env 파일”을 따로 두는 편이 안전합니다.
3) 크론의 기본 셸은 /bin/sh: Bash 문법이 깨진다
많은 시스템에서 크론은 기본적으로 /bin/sh 로 커맨드를 실행합니다. 그런데 스크립트가 Bash 전용 문법을 쓰면 크론에서만 실패합니다.
대표적인 예:
[[ ... ]]테스트source키워드- 배열 문법
set -o pipefail지원 여부
3-1. crontab에 SHELL을 지정하거나, 스크립트 shebang을 고정
crontab 상단에 지정:
SHELL=/bin/bash
* * * * * /usr/local/bin/myjob.sh >> /var/log/myjob.log 2>&1
또는 스크립트 첫 줄을 명확히:
#!/usr/bin/env bash
그리고 Bash 스크립트라면 실패 전파를 확실히 하기 위해 아래 패턴을 추천합니다.
#!/usr/bin/env bash
set -euo pipefail
trap 'echo "failed at line=$LINENO"' ERR
some_command | another_command
set -e 가 기대대로 동작하지 않는 파이프라인/서브셸 케이스는 별도 함정이 많습니다. 아래 글을 함께 보면 “크론에서는 조용히 성공처럼 보이는데 실제론 실패” 같은 상황을 줄일 수 있습니다.
4) 경로/작업 디렉터리 문제: 상대경로가 깨진다
크론은 보통 작업 디렉터리가 / 이거나, 사용자의 홈이 아닐 수 있습니다. 그래서 상대경로(./data, ../config)는 크론에서 자주 깨집니다.
4-1. 스크립트 시작 시 작업 디렉터리를 고정
#!/usr/bin/env bash
set -euo pipefail
cd /opt/app
./bin/run-task.sh
또는 “스크립트 위치 기준”으로 이동:
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR=$(cd -- "$(dirname -- "$0")" && pwd)
cd "$SCRIPT_DIR"
./run-task.sh
5) 권한/실행 비트/소유자: 실행 자체가 막힌다
- 스크립트에 실행 비트가 없거나
- 실행 사용자가 파일/디렉터리에 접근 권한이 없거나
sudo가 비밀번호를 요구하는데 TTY가 없어 멈추는 경우
점검:
ls -l /usr/local/bin/myjob.sh
id
크론에서 sudo 를 써야 한다면 sudoers 에서 NOPASSWD를 최소 권한으로 부여하고, 가능한 한 “크론은 해당 권한을 가진 계정으로 직접 실행”하는 구조가 낫습니다.
6) crontab 문법/시간대/개행 함정
6-1. 시간대(TZ) 불일치
서버가 UTC인데 사람은 KST로 보고 있는 경우가 흔합니다.
- 시스템 시간 확인
date
timedatectl
- 크론에
TZ=Asia/Seoul을 명시하거나(지원되는 구현에서), 시스템 시간대를 맞춥니다.
6-2. 개행/CRLF 문제
윈도우에서 만든 스크립트가 CRLF면 크론에서 bad interpreter 류로 터질 수 있습니다.
file /usr/local/bin/myjob.sh
sed -n '1p' /usr/local/bin/myjob.sh
필요 시 변환:
sed -i 's/\r$//' /usr/local/bin/myjob.sh
7) 재현 가능한 “진단용 크론” 템플릿
문제가 복잡할수록, 한 번에 많은 걸 하려다 원인을 놓칩니다. 아래처럼 “환경 덤프 + 최소 작업”으로 재현하세요.
* * * * * env | sort > /tmp/cron-env.txt
* * * * * (date; whoami; pwd; command -v python3) >> /tmp/cron-probe.log 2>&1
cron-env.txt를 터미널에서의env | sort결과와 비교하면 차이가 바로 보입니다.
8) 운영에서 재발 방지: 스크립트를 서비스처럼 다루기
크론을 단순 스케줄러로만 쓰면, 실패 감지/재시도/중복 실행 방지가 약합니다. 최소한 아래 3가지는 갖추는 편이 좋습니다.
8-1. 락으로 중복 실행 방지
* * * * * flock -n /tmp/myjob.lock /usr/local/bin/myjob.sh >> /var/log/myjob.log 2>&1
-n은 이미 실행 중이면 즉시 종료
8-2. 타임아웃으로 무한 대기 방지
* * * * * timeout 300 /usr/local/bin/myjob.sh >> /var/log/myjob.log 2>&1
8-3. 실패 시 알림(최소 메일이라도)
크론의 MAILTO 를 이용하면 간단히 알림을 받을 수 있습니다.
MAILTO=ops@example.com
* * * * * /usr/local/bin/myjob.sh
메일 환경이 없다면, 슬랙 웹훅/알림 시스템으로 보내는 방식으로 확장하세요.
9) 빠른 체크리스트(이 순서대로 보면 대부분 잡힌다)
- 시스템 로그에서 크론 트리거 여부 확인(
syslog,/var/log/cron,journalctl) - 크론 엔트리에
>> ... 2>&1로 로그 고정 PATH,HOME,LANG,TZ등 환경변수 차이 확인(env | sort비교)- 상대경로 제거,
cd로 작업 디렉터리 고정 - 기본 셸이
/bin/sh인지 확인하고SHELL=/bin/bash또는 shebang 고정 - 실행 파일 절대 경로 사용, 권한/소유자/CRLF 점검
flock,timeout, 알림으로 운영 안정성 보강
크론 이슈는 “원인이 하나”인 경우도 많지만, 실제로는 PATH 문제와 셸 문법 문제가 동시에 숨어 있는 식으로 겹치는 경우가 흔합니다. 위 템플릿대로 로그와 환경을 먼저 고정해두면, 다음부터는 같은 문제를 훨씬 빠르게 끝낼 수 있습니다.