Published on

리눅스 logrotate 실패? 권한·SELinux 7분 해결

Authors

서버 운영 중 디스크가 갑자기 차오르거나, 특정 로그 파일이 며칠치씩 쌓여 있는 걸 발견했는데 logrotate가 조용히 실패하고 있는 경우가 많습니다. 특히 RHEL/CentOS/Rocky/Alma 계열처럼 SELinux가 활성화된 환경에서는 “권한은 맞는데도” 회전이 안 되는 일이 자주 발생합니다.

이 글은 logrotate 실패를 7분 내로 진단하는 흐름으로 구성했습니다.

  • 1분: 실패 여부와 에러 메시지 확보
  • 3분: 권한/소유자/디렉터리 권한 점검
  • 3분: SELinux 컨텍스트/정책 점검 및 복구

로그가 쌓여 디스크가 가득 차는 상황이라면, 원인 진단과 함께 inode까지 같이 확인하는 게 좋습니다. 디스크 용량은 남았는데 로그 생성이 실패하는 케이스도 있어요. 필요하면 이 글도 같이 보세요: No space left on device인데 용량 남을 때 - inode 0% 해결

1) 먼저: logrotate가 실제로 실패했는지 확인

systemd 타이머/크론 확인

배포판에 따라 logrotate는 systemd timer 또는 cron으로 실행됩니다.

systemctl list-timers | grep -i logrotate || true
systemctl status logrotate.timer logrotate.service 2>/dev/null || true

# cron 기반일 수도 있음
ls -al /etc/cron.daily/ | grep -i logrotate || true

직접 실행해서 에러를 “보이게” 만들기

-d는 디버그(실제 변경 없음), -v는 상세 로그입니다. 실제로 돌려야 하면 -f를 씁니다.

# 설정 파싱/대상 확인 (실제 회전 없음)
logrotate -d -v /etc/logrotate.conf

# 지금 당장 강제 회전 (주의: 실제로 파일 변경)
logrotate -f -v /etc/logrotate.conf

상태 파일(lock 비슷한 역할) 확인

logrotate는 상태 파일에 마지막 회전 시점을 기록합니다.

cat /var/lib/logrotate/logrotate.status 2>/dev/null || true

상태 파일이 손상되거나 권한이 꼬이면 회전이 건너뛰어지거나 실패할 수 있습니다.

2) 에러 메시지별 “가장 흔한” 원인 맵

logrotate -v 출력이나 /var/log/messages, journalctl에 아래 같은 메시지가 자주 보입니다.

  • error: skipping ... because parent directory has insecure permissions
  • Permission denied
  • failed to rename
  • error opening ...: Permission denied
  • SELinux is preventing ... 혹은 AVC denials

이 중 상위 80%는 아래 두 갈래로 정리됩니다.

  1. 권한/소유자/디렉터리 권한 문제: logrotate는 보통 root로 실행되지만, “회전된 파일을 특정 유저로 생성”하거나 “특정 디렉터리에 쓸 수 있어야” 합니다.
  2. SELinux 컨텍스트 문제: 권한이 맞아도 라벨이 틀리면 차단됩니다.

이제부터는 가장 빠른 순서(권한 → SELinux)로 점검합니다.

3) 권한 이슈 3분 컷 체크리스트

3-1) insecure permissions로 스킵될 때

대표적으로 Nginx/Apache 로그에서 자주 보입니다.

예시 메시지(형태는 환경마다 조금 다름):

  • skipping "/var/log/nginx/access.log" because parent directory has insecure permissions

원인: 로그 디렉터리(예: /var/log/nginx)가 group 또는 other에 쓰기 가능(w)이면, logrotate가 안전하지 않다고 판단해 스킵할 수 있습니다.

# 디렉터리 권한 확인
stat -c '%A %U:%G %n' /var/log/nginx

# 권장: 0755 또는 더 엄격하게
chmod 0755 /var/log/nginx
chown root:root /var/log/nginx

만약 해당 디렉터리가 특정 서비스 유저가 써야 한다면, “디렉터리는 안전하게 유지”하면서 “파일 생성만 서비스 유저로” 하도록 create를 올바르게 설정하는 편이 안전합니다.

3-2) create/su 설정 불일치

logrotate는 회전 후 새 로그 파일을 만들 때 create 지시어를 사용합니다. 이때 소유자/그룹/퍼미션이 실제 서비스가 기대하는 값과 다르면 서비스가 새 파일에 쓰지 못해 장애가 납니다.

Nginx 예시:

/var/log/nginx/*.log {
  daily
  rotate 14
  missingok
  notifempty
  compress
  delaycompress

  # systemd 환경에서 권장: logrotate가 어떤 유저/그룹으로 동작할지 명시
  su root adm

  # 회전 후 새 파일 생성 권한/소유자
  create 0640 nginx adm

  sharedscripts
  postrotate
    # nginx reopen
    /bin/systemctl kill -s USR1 nginx.service >/dev/null 2>&1 || true
  endscript
}

포인트:

  • su는 해당 블록에서 logrotate가 어떤 권한으로 파일 작업을 수행할지 지정합니다.
  • create는 새로 생성될 로그 파일의 모드/소유자/그룹을 지정합니다.
  • 서비스가 nginx 유저로 로그를 쓰는데 새 파일이 root:root로 만들어지면, 다음 로그 쓰기에서 Permission denied가 납니다.

3-3) rename/copytruncate 선택 실수

회전 방식은 크게 두 가지가 많습니다.

  • rename 기반: 기존 파일을 이름 변경하고 새 파일 생성. 일반적으로 더 안전/권장.
  • copytruncate: 파일을 복사한 뒤 원본을 truncate. 로그를 열어둔 프로세스를 재시작/시그널로 reopen하기 어려울 때 사용.

failed to rename가 뜬다면 다음을 점검하세요.

  • 로그 파일이 다른 파일시스템으로 마운트된 경로인지
  • 권한/SELinux로 rename이 막힌 건 아닌지
  • 컨테이너/볼륨 환경에서 atomic rename이 제약되는지

copytruncate는 편하지만, 회전 순간에 로그 유실/중복이 생길 수 있어 가급적 서비스의 reopen(예: USR1) 방식과 함께 rename을 쓰는 편이 좋습니다.

3-4) 실제 서비스 유저가 새 로그에 쓸 수 있는지 즉시 검증

# 새 파일 소유자/권한 확인
ls -al /var/log/nginx | head

# 서비스 유저로 쓰기 테스트 (운영에서는 주의)
sudo -u nginx sh -c 'echo test >> /var/log/nginx/access.log'

여기서 막히면 logrotate 설정(create) 또는 디렉터리 권한이 원인입니다.

4) SELinux 이슈 3분 컷 체크리스트

권한이 “완벽해 보이는데” 계속 실패하면 SELinux를 의심해야 합니다. 특히 /var/log 밖(예: /data/logs, /srv/logs)에 로그를 두는 경우 SELinux 라벨이 기본값이라 회전/생성이 막힐 수 있습니다.

4-1) SELinux 모드 확인

getenforce
sestatus

Enforcing면 차단이 실제로 발생할 수 있습니다.

4-2) AVC 거부 로그 확인

# 최근 AVC 확인
ausearch -m avc -ts recent 2>/dev/null | tail -n 50

# systemd journal에서 selinux 관련 필터링
journalctl -t setroubleshoot --no-pager | tail -n 200

로그에 특정 경로(/data/logs/app.log 등)와 denied가 보이면 거의 확정입니다.

4-3) 파일 컨텍스트(라벨) 확인

ls -Z /var/log 2>/dev/null | head
ls -Z /data/logs 2>/dev/null | head

# 특정 파일만
ls -Z /data/logs/app.log 2>/dev/null || true

/var/log 아래는 보통 var_log_t 계열 라벨이 붙습니다. 반면 커스텀 경로는 default_t 같은 애매한 라벨로 남아 있는 경우가 많습니다.

4-4) 가장 안전한 해결: 올바른 파일 컨텍스트 지정

임시로 chcon만 하면 재부팅/복구 작업에서 원복될 수 있습니다. 영구적으로는 semanage fcontextrestorecon이 정석입니다.

/data/logs를 로그 디렉터리로 쓰는 예시:

# semanage가 없다면 설치
# RHEL 계열
sudo dnf install -y policycoreutils-python-utils || sudo yum install -y policycoreutils-python

# /data/logs 및 하위 파일을 var_log_t로 지정
sudo semanage fcontext -a -t var_log_t '/data/logs(/.*)?'

# 실제 라벨 적용
sudo restorecon -Rv /data/logs

# 확인
ls -Zd /data/logs
ls -Z /data/logs | head

이후 다시 실행:

logrotate -f -v /etc/logrotate.conf

4-5) 정말 급할 때만: Enforcing 임시 해제

운영 환경에서 권장하진 않지만, 원인이 SELinux인지 10초 내 확인하려면 다음이 빠릅니다.

sudo setenforce 0
logrotate -f -v /etc/logrotate.conf
sudo setenforce 1

setenforce 0에서만 성공한다면 SELinux 라벨/정책 문제입니다. 이 경우 위의 semanage fcontext 방식으로 해결하세요.

5) 자주 놓치는 설정 실수 5가지

5-1) postrotate 스크립트 실패로 전체가 실패하는 경우

postrotate에서 서비스 reload/reopen이 실패하면 회전 자체가 실패로 기록될 수 있습니다.

postrotate
  /bin/systemctl kill -s USR1 nginx.service >/dev/null 2>&1 || true
endscript

|| true로 “로그 reopen 실패가 회전 실패로 번지지 않게” 만드는 게 실무에서 유용합니다.

5-2) sharedscripts 유무

와일드카드(/var/log/nginx/*.log)로 여러 파일을 회전할 때 postrotate가 파일마다 실행되면 불필요하게 여러 번 reload될 수 있습니다.

  • sharedscripts 없음: 파일마다 postrotate 실행
  • sharedscripts 있음: 블록당 한 번 실행

5-3) dateext와 외부 도구의 파일 패턴 충돌

dateext를 켜면 회전 파일명이 access.log-20260224 같은 형태로 바뀝니다. 백업/수집 도구가 *.log.1만 기대하면 누락이 생길 수 있어요.

5-4) compress와 애플리케이션의 tail/파서

압축된 로그(.gz)를 애플리케이션이 직접 읽는 구조라면, 압축 시점(delaycompress)을 고려해야 합니다.

5-5) 컨테이너 환경에서 logrotate를 “호스트에서” 돌리는 문제

쿠버네티스에서는 보통 stdout/stderr 기반 수집을 권장합니다. 그래도 파일 로그를 회전해야 한다면, 볼륨 마운트 권한/SELinux(노드 정책)/SecurityContext까지 함께 봐야 합니다. 쿠버네티스 트러블슈팅 흐름이 필요하면 이 글도 도움이 됩니다: EKS에서 kubectl exec·logs가 안 될 때 진단법

6) “바로 복붙” 가능한 진단 스크립트

아래는 특정 로그 파일 하나를 대상으로 권한과 SELinux를 빠르게 훑는 커맨드 묶음입니다. 경로만 바꿔 실행하세요.

LOG=/var/log/nginx/access.log
DIR=$(dirname "$LOG")

echo "[1] logrotate dry-run"
logrotate -d -v /etc/logrotate.conf 2>&1 | tail -n 50

echo "[2] file/dir permissions"
stat -c '%A %a %U:%G %n' "$DIR" "$LOG" 2>/dev/null || true

echo "[3] selinux mode"
getenforce 2>/dev/null || true

echo "[4] selinux context"
ls -Zd "$DIR" 2>/dev/null || true
ls -Z "$LOG" 2>/dev/null || true

echo "[5] recent AVC denials"
ausearch -m avc -ts recent 2>/dev/null | tail -n 20 || true

7) 결론: 해결 우선순위는 “디렉터리 권한 → create/su → SELinux 라벨”

logrotate 실패는 겉으로는 단순히 “회전이 안 됨”이지만, 실제 원인은 대부분 다음 셋 중 하나입니다.

  1. 로그 디렉터리 권한이 안전하지 않아 insecure permissions로 스킵
  2. create/su 설정 불일치로 새 로그 파일에 서비스가 쓰지 못함
  3. SELinux 컨텍스트가 맞지 않아 rename/create가 차단됨

이 순서대로 보면 시간 낭비 없이 빠르게 복구할 수 있습니다. 특히 SELinux는 끄는 게 아니라, semanage fcontextrestorecon으로 “의도한 라벨”을 부여하는 게 정석입니다.