환경  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 를 같이 둔다.
  • 회전 중 오류가 나도 상태 파일은 갱신된다. 날짜만 보지 말고 종료 코드까지 확인한다.

로그 회전은 잘 돌 때 존재감이 없습니다. 그래서 멈춰도 알아채기 어렵고, 알아챌 때는 이미 디스크가 다 찬 뒤죠. 서비스를 새로 올린 날이나 로그 경로를 만진 날, 위 다섯 줄만 찍어 보면 됩니다. 오 분이면 끝나고, 그 오 분이 새벽에 울리는 전화를 막습니다.