서버를 오래 돌리다 보면 로그가 디스크를 다 먹는 상황을 한 번씩 만난다. 특히 애플리케이션이 직접 파일에 붙여 쓰는 catalina.out 같은 로그는 방치하면 수십 GB까지 커진다. 예전에 웹, WAS 점검하러 들어가면 /logs 파티션만 100% 차 있는 서버가 꼭 하나씩 있었다. logrotate 설정 몇 줄이면 끝나는 일인데, copytruncate와 create를 잘못 고르면 로테이션은 도는데 옛 파일에만 계속 기록되는 함정에 걸린다. 이 글은 그 지점을 정리한다.
결론부터 말하자면, logrotate의 기본 방식인 create는 원본을 .1로 이름만 바꾸고 같은 이름의 빈 파일을 새로 만든다. 이 방식은 애플리케이션이 파일을 다시 열어야 새 파일에 쓴다. 다시 열지 못하는 프로세스(대표적으로 catalina.out에 붙는 Tomcat)라면 copytruncate로 원본을 복사한 뒤 내용만 비워야 한다. 아래에서 두 방식의 동작과 Tomcat, Nginx, 일반 애플리케이션별 설정을 차례로 본다.
1. logrotate가 하는 일
logrotate는 로그 파일을 주기나 크기 기준으로 잘라 보관하고, 오래된 것을 압축하거나 지우는 도구다. 대부분의 배포판에 기본 설치돼 있고 cron 또는 systemd timer가 하루 한 번 실행한다. 실행 시각과 마지막 로테이션 기록은 상태 파일에 남는다.
$ rpm -q logrotate # RHEL 계열 logrotate-3.20.1-3.el9.x86_64 $ cat /etc/cron.daily/logrotate #!/bin/sh /usr/sbin/logrotate /etc/logrotate.conf $ ls /etc/logrotate.d/ chrony nginx sssd-common syslog
전역 설정은 /etc/logrotate.conf에 있고, 개별 서비스 설정은 /etc/logrotate.d/ 아래에 파일 하나씩 둔다. 상태 파일은 /var/lib/logrotate/logrotate.status(배포판에 따라 /var/lib/logrotate.status)에 기록되며, 여기에 각 로그의 마지막 로테이션 날짜가 저장된다.
2. 핵심 지시어
설정 파일에 쓰는 지시어는 많지만 운영에서 쓰는 건 몇 개로 좁혀진다. 하나씩 정의하고 넘어간다.
주기와 크기: daily, weekly, size, maxsize
daily, weekly, monthly는 시간 기준 로테이션이다. size 100M은 파일이 그 크기를 넘으면 자른다. size는 시간 지시어와 함께 쓸 수 없고, 크기만 본다.
maxsize 100M는 다르다. 주기가 되지 않아도 크기를 넘으면 자르고, 크기에 못 미치면 주기에 맞춰 자른다. "하루 한 번, 단 그 전에 100M를 넘으면 바로" 같은 요구에는 daily와 maxsize를 같이 쓴다. 반대로 minsize는 주기가 됐어도 최소 크기에 못 미치면 넘긴다.
보관 개수: rotate
rotate 7은 로테이션된 파일을 7개까지 보관하고 그다음부터 오래된 것을 지운다. 기본값은 0이라 이 값을 안 주면 로테이션하면서 바로 지워버린다. 보관이 목적이라면 rotate를 반드시 명시한다.
압축: compress, delaycompress
compress는 로테이션된 파일을 gzip으로 압축한다. delaycompress는 압축을 한 주기 미룬다. 즉 .1은 압축하지 않고 두었다가 다음 로테이션에서 .2.gz로 만든다. 프로세스가 로테이션 직후에도 .1에 잠깐 더 쓸 수 있는 경우, 압축을 미뤄 내용 손상을 피한다.
안전장치: missingok, notifempty, dateext
missingok은 로그 파일이 없어도 에러 없이 넘어간다. notifempty는 파일이 비어 있으면 로테이션하지 않는다. dateext는 .1 대신 -20260916 같은 날짜를 붙여, 어느 날짜 로그인지 이름만 봐도 알게 한다.
스크립트: postrotate, sharedscripts
postrotate ... endscript 사이에 로테이션 후 실행할 명령을 넣는다. Nginx에 USR1 신호를 보내 로그 파일을 다시 열게 하는 것이 대표 용도다. 와일드카드로 여러 파일을 잡을 때 sharedscripts를 넣으면 스크립트를 파일마다가 아니라 한 번만 실행한다.
3. copytruncate와 create의 차이
이 둘이 로테이션의 핵심이고, 함정도 여기서 나온다. 리눅스에서 프로세스는 파일 이름이 아니라 열어둔 파일 디스크립터로 쓴다. 로그 파일 이름을 바꿔도, 프로세스가 그 디스크립터를 쥐고 있으면 이름 바뀐 옛 파일에 계속 쓴다.
create (기본 방식)
원본 app.log를 app.log.1로 이름을 바꾸고, app.log라는 빈 파일을 새로 만든다. 문제는 프로세스가 아직 app.log.1이 된 옛 디스크립터를 쥐고 있다는 점이다. 프로세스가 파일을 닫고 다시 열어야(로그 프레임워크의 재오픈, 또는 USR1 같은 신호) 새 app.log에 쓰기 시작한다.
# 로테이션은 됐는데 새 파일 크기가 0에서 안 늘어나는 증상 $ ls -l /app/logs/ -rw-r--r-- 1 tomcat tomcat 0 Sep 16 03:20 app.log -rw-r--r-- 1 tomcat tomcat 524288000 Sep 16 03:20 app.log.1 # 프로세스가 지운/이름 바뀐 파일을 계속 붙들고 있는지 확인 $ ls -l /proc/$(pgrep -f app.jar)/fd | grep app.log l-wx------ 1 root root 64 Sep 16 03:25 267 -> /app/logs/app.log.1
app.log는 0바이트인데 실제 로그는 app.log.1로 계속 들어간다. 이게 "로테이션 후에도 옛 파일에 계속 기록되는" 함정이다. 애플리케이션이 파일을 다시 열 방법이 있으면 create가 정답이다. Log4j2, Logback 같은 프레임워크는 자체 롤링을 쓰거나 재오픈을 지원하므로 create와 잘 맞는다.
copytruncate
원본을 app.log.1로 복사한 뒤, 원본 app.log를 그 자리에서 내용만 0으로 비운다. 디스크립터와 inode가 그대로라 프로세스는 하던 대로 app.log에 계속 쓴다. 파일을 다시 열지 못하는 프로세스에 쓴다.
대가가 있다. 복사와 절단(truncate) 사이 아주 짧은 순간에 쓰인 로그는 유실될 수 있다. 또 복사본을 만드는 동안 순간적으로 디스크를 원본 크기만큼 더 쓴다. 그래서 copytruncate는 "재오픈이 안 되는 레거시"에 한정하는 것이 맞다.
4. Tomcat catalina.out 설정
Tomcat의 catalina.out은 시작 스크립트가 표준출력을 리다이렉트해 붙이는 파일이라, Tomcat 자체는 이 파일을 다시 열지 못한다. create로 돌리면 위의 함정에 그대로 걸린다. copytruncate를 쓴다.
$ sudo vi /etc/logrotate.d/tomcat
/opt/tomcat/logs/catalina.out {
copytruncate
daily
maxsize 200M
rotate 14
compress
delaycompress
missingok
notifempty
dateext
su tomcat tomcat
}
참고로 catalina.out은 server.xml의 접근 로그나 logging.properties의 애플리케이션 로그와 다르다. 후자는 Tomcat이 자체적으로 날짜별 파일을 만들므로 logrotate로 또 자르지 않는다. logrotate 대상은 자체 롤링이 없는 catalina.out으로 좁힌다.
5. Nginx 설정
Nginx는 USR1 신호를 받으면 로그 파일을 다시 연다. 그래서 create로 로테이션하고 postrotate에서 신호를 보내는 것이 정석이다. 배포판 패키지가 /etc/logrotate.d/nginx를 이미 깔아두는데, 형태는 이렇다.
/var/log/nginx/*.log {
daily
rotate 14
compress
delaycompress
missingok
notifempty
create 0640 nginx adm
sharedscripts
postrotate
[ -f /var/run/nginx.pid ] && kill -USR1 `cat /var/run/nginx.pid`
endscript
}
sharedscripts가 있어 access.log와 error.log를 둘 다 자른 뒤 USR1을 딱 한 번 보낸다. delaycompress가 있는 이유는, 신호를 보내도 워커가 옛 디스크립터를 닫는 데 짧은 시간이 걸려 .1에 잠깐 더 쓸 수 있기 때문이다. 압축을 한 주기 미뤄 그 사이 손상을 막는다.
6. 적용 확인과 디버깅
설정을 바꾼 뒤에는 -d(디버그, 실제로 자르지 않고 계획만 출력)로 먼저 확인한다. 실제 강제 실행은 -f다.
# 실제 변경 없이 무엇을 할지만 출력 $ sudo logrotate -d /etc/logrotate.d/tomcat reading config file /etc/logrotate.d/tomcat Rotating pattern: /opt/tomcat/logs/catalina.out after 1 days (14 rotations) empty log files are not rotated, old logs are removed considering log /opt/tomcat/logs/catalina.out Now: 2026-09-16 03:30 Log does not need rotating (log has been already rotated) # 주기를 무시하고 강제 로테이션 $ sudo logrotate -f /etc/logrotate.d/tomcat $ ls -l /opt/tomcat/logs/ -rw-r--r-- 1 tomcat tomcat 0 Sep 16 03:31 catalina.out -rw-r--r-- 1 tomcat tomcat 198M Sep 16 03:31 catalina.out-20260916
여기서 한 번 걸리는 지점이 있다. su 지시어 없이 root가 아닌 디렉터리를 자르려 하면 최신 logrotate는 권한 경고를 내고 넘어간다. 로그 디렉터리 소유자에 맞춰 su tomcat tomcat처럼 명시하면 풀린다.
error: skipping "/opt/tomcat/logs/catalina.out" because parent directory has insecure permissions ... Set "su" directive in config file to tell logrotate which user/group should be used for rotation.
또 하나, 로테이션이 아예 안 도는 것처럼 보이면 상태 파일의 마지막 날짜를 본다. 같은 날 이미 돌았다고 기록돼 있으면 -f 없이는 다시 돌지 않는다.
$ sudo grep catalina /var/lib/logrotate/logrotate.status "/opt/tomcat/logs/catalina.out" 2026-9-16-3:0:0
7. 정리
파일을 다시 열 수 있는 프로세스(Nginx, 재오픈을 지원하는 로그 프레임워크)는 create에 postrotate 재오픈을 붙인다. 다시 열지 못하는 catalina.out 부류는 copytruncate로 간다. 보관은 rotate로 개수를, compress와 delaycompress로 용량과 안전을 잡고, 크기 상한이 필요하면 daily에 maxsize를 더한다. 설정 후 logrotate -d로 계획을 확인하고, 로테이션이 돌았는데 새 파일이 0바이트에서 멈춰 있으면 /proc/PID/fd로 프로세스가 옛 파일을 붙들고 있는지부터 본다. 버전 번호와 출력은 저장소, 배포판 상태에 따라 다를 수 있다.