디스크가 로그로 차기 전에, 로그 회전 점검 목록
환경 logrotate 3.21 · Debian 계열 기준 · 2026-08-31 확인
새벽에 서비스가 멈춥니다. 500 이 뜨고, 재시작해도 몇 분 뒤 또 멈추죠. df 를 찍어 보면 루트 파티션이 100%. 범인은 몇 달 동안 한 번도 잘리지 않은 로그 파일 하나입니다. 회전 설정은 분명히 넣어 뒀는데 말이죠. 로그 회전은 잘 돌 때 아무 소리도 내지 않고, 멈춰도 똑같이 아무 소리를 내지 않습니다. 그래서 확인은 디스크가 차기 전에 해야 합니다.
이 목록을 펴는 건 사고가 난 다음이 아닙니다
로그가 디스크를 채우는 사고에는 공통점이 하나 있습니다. 터지는 순간까지 아무 경고가 없다는 겁니다. 용량이 절반 찼을 때도, 90% 를 넘겼을 때도 서비스는 멀쩡히 돕니다. 그러다 마지막 한 줄을 못 쓰는 순간 전부 무너지죠.
그래서 이 목록은 장애 대응용이 아닙니다. 새 서비스를 올린 날, 로그 형식이나 경로를 바꾼 날, 웹서버를 다른 버전으로 갈아 끼운 날처럼 회전 설정이 현실과 어긋나기 시작하는 시점에 한 번 훑는 용도입니다.
특히 위험한 건 '예전에 잘 돌았던' 설정입니다. 로그 경로가 바뀌었는데 회전 설정은 옛 경로를 가리키고 있거나, 애플리케이션이 로그 파일을 여는 방식이 달라졌는데 회전 방식은 그대로인 경우죠. 두 경우 다 문법 오류가 아니라서 설정 검사로는 안 걸립니다.
아래 항목들은 대부분 명령 한 줄이면 끝납니다. 다 합쳐도 오 분이 안 걸리는데, 그 오 분이 새벽 전화 한 통을 막습니다.
회전 설정이 틀렸다는 신호는 디스크가 다 찰 때까지 나오지 않습니다. 신호를 기다리지 말고 먼저 봐야 합니다.
설정이 있는 것과 회전이 도는 것은 다른 이야기입니다
logrotate 는 스스로 돌지 않습니다. 바깥에서 누가 불러 줘야 도는 프로그램이죠. 보통은 하루 한 번 cron 작업으로 불리고, systemd 를 쓰는 시스템이면 logrotate.timer 가 그 역할을 합니다.
그러니 첫 확인은 설정 파일이 아니라 그 호출자입니다. 타이머가 비활성이거나 cron 항목이 지워졌으면 /etc/logrotate.d 아래를 아무리 잘 써 놔도 한 줄도 실행되지 않습니다.
다음으로 볼 건 상태 파일입니다. logrotate 는 어느 로그를 언제 마지막으로 돌렸는지 여기에 기록합니다. 기본 경로는 /var/lib/logrotate/status 이고, 배포판에 따라 다른 자리에 둘 수도 있어서 실행 명령의 -s 옵션을 같이 봐야 확실합니다.
상태 파일에서 내가 걱정하는 로그 경로를 찾아 날짜를 봅니다. 몇 달 전 날짜가 찍혀 있거나 아예 항목이 없으면, 그 로그는 회전 대상에 들어가 있지 않은 겁니다.
systemctl list-timers logrotate.timer
# 타이머가 없는 시스템이면
ls -l /etc/cron.daily/logrotate
# 마지막으로 언제 돌았는지
grep -F "/var/log/nginx/access.log" /var/lib/logrotate/status회전을 불러 주는 쪽과 마지막 실행 기록을 먼저 확인합니다
손대기 전에 예행 연습부터 시켜 봅니다
설정을 고친 다음 다음 날 새벽까지 기다렸다가 결과를 확인하는 건 너무 느립니다. logrotate 에는 아무것도 바꾸지 않고 무엇을 할지만 출력하는 모드가 있습니다.
-d 옵션을 붙이면 로그 파일도 건드리지 않고 상태 파일도 갱신하지 않습니다. 어떤 파일을 회전 대상으로 봤는지, 왜 이번에는 건너뛰기로 했는지가 그대로 나옵니다.
출력에서 눈여겨볼 문구는 두 가지입니다. 하나는 특정 파일을 건너뛴다는 줄이고, 다른 하나는 설정을 읽다가 낸 오류 줄입니다. 둘 다 평소 실행에서는 아무 데도 안 남고 지나갑니다.
반대로 지금 당장 한 번 돌려 보고 싶으면 강제 실행이 있습니다. 다만 이건 진짜로 파일을 자르고 상태 파일도 갱신하니, 운영 중인 서버에서는 예행 연습으로 먼저 훑은 다음에 씁니다.
# 아무것도 바꾸지 않고 계획만 출력
logrotate -d /etc/logrotate.conf
# 문제 있는 줄만 추리기
logrotate -d /etc/logrotate.conf 2>&1 | grep -Ei "error|skipping|does not exist"
# 조건과 무관하게 지금 한 번 돌리기 (실제로 자릅니다)
logrotate -f /etc/logrotate.d/nginx-d 는 상태 파일까지 그대로 두므로 운영 서버에서도 안전합니다
-d 는 로그도 상태 파일도 건드리지 않습니다. 설정을 고친 날 이 한 줄을 찍는 습관이면 대부분의 사고가 걸립니다.
크기 조건은 파일이 커진 순간에 검사되지 않습니다
size 100M 을 걸어 두면 로그가 100MB 를 넘는 순간 잘릴 거라고 생각하기 쉽습니다. 그런데 logrotate 는 상주하지 않습니다. 하루 한 번 불려 나온 그 순간에만 크기를 재죠.
그래서 하루 사이에 로그가 수십 기가로 부는 서비스라면, 크기 조건을 아무리 낮게 잡아도 다음 실행 시각 전에 디스크가 먼저 찹니다. 이럴 때 고쳐야 하는 건 조건값이 아니라 호출 주기입니다.
조건을 쓰는 방식도 헷갈리는 자리입니다. size 는 시간 조건과 함께 쓸 수 없고, 시간 조건 뒤에 적으면 마지막 회전 시각을 무시하고 크기만 봅니다. 반면 maxsize 는 시간 조건과 같이 쓰면서 '주기 전이라도 이 크기를 넘으면 자른다'로 동작하고, minsize 는 '크기를 넘겨도 주기 전에는 자르지 않는다'입니다.
평소에는 하루 한 번, 폭주하는 날에는 그 전에라도 자르고 싶다면 daily 와 maxsize 를 같이 두는 쪽이 의도에 맞습니다. 물론 그것도 호출 주기가 하루 한 번이면 하루 한 번만 검사됩니다.
/var/log/app/*.log {
daily
maxsize 200M
rotate 14
compress
delaycompress
missingok
notifempty
}maxsize 는 주기를 지키되 그 전에라도 커지면 자릅니다. 다만 검사 시점은 logrotate 가 불려 나올 때입니다
이름이 바뀐 파일에 프로세스가 계속 쓰고 있을 때
기본 동작은 로그 파일의 이름을 바꾸고 새 파일을 만드는 겁니다. 문제는 그 파일을 이미 열어 둔 프로세스입니다. 열려 있는 건 이름이 아니라 파일 그 자체라서, 이름이 access.log.1 로 바뀌어도 프로세스는 계속 그쪽에 씁니다.
그러면 새로 만들어진 access.log 는 영원히 0바이트로 남고, 실제 로그는 회전된 파일에 계속 쌓입니다. 며칠 뒤 그 파일이 압축 대상이 되면서 디스크가 이상해지고, 그제야 눈에 띄죠.
그래서 회전 뒤에 프로세스에게 '파일을 다시 열어라'라고 알려 줘야 합니다. nginx 는 이 신호가 USR1 입니다. 마스터 프로세스가 열려 있던 로그 파일을 전부 다시 열고 워커에게도 다시 열라고 시킵니다. 순서가 중요한데, 이름을 먼저 바꾸고 그다음에 신호를 보냅니다. logrotate 의 postrotate 블록이 정확히 그 자리입니다.
여러 파일이 한 패턴에 걸릴 때는 sharedscripts 를 같이 둡니다. 이게 없으면 회전되는 파일 수만큼 신호를 보내고, 하나라도 실패하면 그 파일에 대한 나머지 동작이 중단됩니다.
/var/log/nginx/*.log {
daily
rotate 30
compress
delaycompress
missingok
notifempty
sharedscripts
postrotate
[ -f /run/nginx.pid ] && kill -USR1 $(cat /run/nginx.pid)
endscript
}이름을 바꾼 뒤 USR1 을 보내면 마스터가 로그를 다시 엽니다
새 로그 파일이 계속 0바이트라면 회전이 안 된 게 아니라, 회전 뒤에 다시 열라고 말해 주지 않은 겁니다.
copytruncate 는 편하지만 마지막 선택지입니다
다시 열라고 시킬 방법이 없는 프로그램도 있습니다. 신호를 받지 않거나, 받아도 로그를 다시 열지 않는 경우죠. 이럴 때 쓰라고 있는 게 copytruncate 입니다. 원본을 복사해 두고 원본 자체를 0바이트로 잘라 버리는 방식이라 파일이 그 자리에 그대로 남습니다.
그래서 프로세스는 아무것도 몰라도 됩니다. 계속 같은 파일에 쓰면 되니까요. 편한 만큼 대가가 있는데, 복사와 자르기 사이에 아주 짧은 틈이 있고 그 사이에 들어온 로그는 사라질 수 있습니다. 문서에도 그렇게 적혀 있습니다.
또 하나, copytruncate 를 쓰면 create 설정이 무효가 됩니다. 원본 파일이 그대로 남으니 새로 만들 파일이 없기 때문이죠. create 로 권한이나 소유자를 지정해 뒀다면 그 설정은 아무 일도 하지 않습니다.
정리하면 신호를 받아 주는 프로그램에는 postrotate 를, 그럴 방법이 없는 프로그램에만 copytruncate 를 씁니다. 접근 로그 몇 줄이 아쉬운 자리인지 아닌지로 판단하면 됩니다.
/var/log/legacy/worker.log {
daily
rotate 7
compress
copytruncate
missingok
}다시 열라고 시킬 수 없는 프로그램에만 씁니다. 잘리는 순간의 몇 줄은 포기하는 설정입니다
권한 때문에 조용히 건너뛰는 자리가 있습니다
설정도 맞고 호출도 되는데 특정 로그만 안 잘리는 일이 있습니다. 권한이 원인인 경우가 많습니다.
logrotate 를 root 로 돌리면서 로그 디렉터리가 일반 사용자 손에 있으면, 그 디렉터리는 su 지시자로 어느 사용자·그룹 자격으로 회전할지 적어 주는 쪽이 안전합니다. 문서도 그렇게 권합니다. 다만 su 에 적은 계정이 create 로 지정한 소유권의 파일을 만들 권한이 없으면 그 자리에서 오류가 납니다.
설정 파일 자체의 권한도 봐야 합니다. include 로 읽어 들이는 설정 파일은 그룹이나 다른 사용자에게 쓰기 권한이 열려 있으면 읽지 않습니다. 배포 스크립트가 파일을 664 로 떨어뜨려 놓고 아무도 눈치채지 못하는 경우가 여기 걸립니다.
마지막으로 로그를 남기는 쪽과 회전하는 쪽의 소유자가 어긋나지 않는지 확인합니다. 어느 쪽이든 -d 출력에 힌트가 남으니, 의심되는 설정 파일 하나만 지정해서 돌려 보면 빠릅니다.
# 설정 파일이 그룹·기타 쓰기 권한을 갖고 있으면 읽히지 않습니다
ls -l /etc/logrotate.d/
chmod 644 /etc/logrotate.d/myapp
# 디렉터리 소유자 확인
ls -ld /var/log/myapp
# 그 설정만 예행 연습
logrotate -d /etc/logrotate.d/myapp권한 문제는 오류로 튀지 않고 건너뛰기로 조용히 지나갑니다
어디까지 맞으면 통과인가
통과 기준은 단순합니다. 예행 연습 출력에 오류와 건너뛰기가 없고, 상태 파일에 대상 로그마다 최근 날짜가 찍혀 있고, 회전된 다음에도 새 로그 파일에 새 줄이 들어오면 됩니다. 이 셋이면 회전은 살아 있는 겁니다.
실제로 한 바퀴 태워 보는 게 제일 확실합니다. 강제 실행으로 한 번 돌린 뒤 회전된 파일이 생겼는지, 새 파일 크기가 0에서 늘어나는지를 봅니다. 새 파일이 계속 0바이트면 다시 열라는 신호가 안 닿은 겁니다.
한 가지 함정을 더 알아 둘 만합니다. 회전 도중에 오류가 나도 logrotate 는 상태 파일을 갱신합니다. 종료 코드는 0이 아니지만 기록은 남죠. 그래서 상태 파일 날짜만 보고 '어제 잘 돌았네'로 넘기면 안 됩니다. 종료 코드를 같이 봐야 합니다.
cron 이나 타이머로 도는 실행의 종료 코드는 아무도 안 봅니다. 회전 명령을 감싸는 스크립트를 쓰고 있다면, 실패했을 때 알림이 가도록 종료 코드를 확인하는 줄 하나를 붙여 두는 편이 낫습니다.
logrotate -f /etc/logrotate.d/nginx
echo "exit=$?"
ls -l /var/log/nginx/
# access.log 가 0바이트에서 늘어나는지
sleep 5; ls -l /var/log/nginx/access.log종료 코드와 새 파일 크기까지 봐야 한 바퀴 확인이 끝납니다
상태 파일에 날짜가 찍혔다고 성공한 게 아닙니다. 오류가 나도 기록은 남습니다.
조치 후 확인할 것
- 회전을 불러 주는 쪽부터 본다. logrotate.timer 가 활성인지, 아니면 cron.daily 항목이 남아 있는지 확인한다. 설정 파일은 호출자가 없으면 한 줄도 실행되지 않는다.
- 설정을 고친 날에는 logrotate -d 로 계획만 출력해 본다. 로그도 상태 파일도 건드리지 않으니 운영 서버에서 그대로 돌려도 된다. 출력에서 error 와 skipping 만 추려 본다.
- 크기 조건은 logrotate 가 불려 나온 순간에만 검사된다. 하루에 디스크를 채울 만큼 로그가 부는 서비스라면 조건값이 아니라 호출 주기를 손봐야 한다.
- 회전 뒤 새 로그가 계속 0바이트면 다시 열라는 신호가 안 닿은 것이다. nginx 는 postrotate 에서 USR1 을 보내고, 여러 파일이 한 패턴에 걸리면 sharedscripts 를 같이 둔다.
- 회전 중 오류가 나도 상태 파일은 갱신된다. 날짜만 보지 말고 종료 코드까지 확인한다.
로그 회전은 잘 돌 때 존재감이 없습니다. 그래서 멈춰도 알아채기 어렵고, 알아챌 때는 이미 디스크가 다 찬 뒤죠. 서비스를 새로 올린 날이나 로그 경로를 만진 날, 위 다섯 줄만 찍어 보면 됩니다. 오 분이면 끝나고, 그 오 분이 새벽에 울리는 전화를 막습니다.

