Skip to content

Split View: 리눅스 로그 운영 완전 가이드: journald, rsyslog, logrotate를 하나로 엮기

|

리눅스 로그 운영 완전 가이드: journald, rsyslog, logrotate를 하나로 엮기

들어가며

로그 때문에 생기는 장애는 두 가지 얼굴을 가집니다. 필요할 때 없거나, 필요 없을 때 디스크를 채웁니다. 둘 다 로그 배관을 설계하지 않고 기본값 그대로 굴린 결과입니다.

이 블로그에는 구조화 로깅 설계가 있고, 그 글은 애플리케이션이 무엇을 어떤 필드로 남겨야 하는지를 다룹니다. 이 글은 그 아래층입니다. 애플리케이션이 뱉은 로그가 호스트에서 어디로 흘러가고, 얼마나 남고, 언제 지워지는가가 주제입니다. 로그의 내용이 아니라 로그의 배관입니다.

다루는 범위는 세 층입니다. systemd-journald가 수집하고, rsyslog가 파일과 원격으로 보내고, logrotate가 파일을 회전시킵니다. 기준은 systemd 250 이상, rsyslog 8.x, logrotate 3.18 이상이며, 배포판별 경로 차이는 그때그때 표기합니다.


1. 로그가 흐르는 경로

현대 리눅스에서 로그의 출발점은 대부분 journald입니다. 입력은 네 갈래입니다.

  • 커널 링 버퍼(/dev/kmsg)
  • syslog 소켓(/dev/log)
  • 네이티브 저널 API
  • 서비스의 표준 출력과 표준 오류

마지막 항목이 중요합니다. systemd 유닛으로 실행되는 프로세스가 화면에 찍는 모든 것은 자동으로 저널에 들어갑니다. 애플리케이션이 파일에 직접 쓰지 않아도 로그가 남는다는 뜻이며, 컨테이너 시대의 표준 관행과도 맞습니다.

저널에서 다음 단계로 나가는 경로는 두 가지입니다.

systemctl status systemd-journald.service
systemctl status rsyslog.service
ls -l /run/systemd/journal/

rsyslog는 보통 imjournal 모듈로 저널을 읽거나, imuxsock으로 소켓을 직접 받습니다. 두 방식을 동시에 켜면 같은 메시지가 두 번 기록되므로 하나만 써야 합니다. 배포판 기본값이 무엇인지 확인하고 시작하세요.

grep -rn 'imjournal\|imuxsock' /etc/rsyslog.conf /etc/rsyslog.d/ 2>/dev/null

배관을 설계할 때 먼저 정해야 할 것은 어디가 단일 진실 원본인가입니다. 선택지는 세 가지입니다. 저널을 원본으로 삼고 파일은 보조로 두거나, 파일을 원본으로 삼고 저널은 최소한만 남기거나, 둘 다 두고 중앙 수집기로 보내는 구성입니다. 세 번째가 가장 흔하지만 가장 중복이 많습니다.

판단 기준은 도구입니다. 팀이 쓰는 조사 도구가 journalctl 중심이면 저널을 원본으로, 파일을 읽는 에이전트나 기존 스크립트가 많으면 파일을 원본으로 두는 편이 자연스럽습니다. 중요한 것은 어느 쪽을 골랐는지 문서에 적어 두는 것입니다. 이것이 정해져 있지 않으면 나중에 로그가 두 곳에 서로 다른 형식으로 쌓이고, 조사할 때마다 어느 쪽이 맞는지 확인하는 시간이 추가됩니다.


2. journalctl — 조회를 먼저 익힌다

설정을 바꾸기 전에 조회부터 능숙해야 합니다. 조회가 빠르면 장애 시간이 줄어듭니다.

journalctl -u nginx.service -n 200 --no-pager
journalctl -u nginx.service -f
journalctl -p err -b
journalctl -k -b -1
journalctl --since '2026-08-15 14:00' --until '2026-08-15 14:30'
journalctl -g 'timeout|refused' --since today
journalctl -o json-pretty -n 1
journalctl --list-boots
journalctl --user-unit myapp.service

옵션의 정확한 의미는 다음과 같습니다.

  • -u는 유닛 또는 패턴으로 필터합니다. --user-unit은 사용자 세션 유닛용입니다.
  • -p는 우선순위 필터이며 이름과 숫자를 모두 받습니다. emerg(0), alert(1), crit(2), err(3), warning(4), notice(5), info(6), debug(7) 순입니다.
  • -b는 부팅 단위 조회입니다. -b -1은 직전 부팅입니다.
  • -k는 커널 메시지만 봅니다.
  • -g는 MESSAGE 필드에 정규식을 적용합니다.
  • -o는 출력 형식이며 short, short-iso, verbose, json, json-pretty, cat 등을 지원합니다.
  • -x는 메시지 카탈로그의 설명을 덧붙입니다. 낯선 오류를 만났을 때 유용합니다.

저널은 구조화 필드를 가지고 있어 필드 단위 조회가 가능합니다. 이것이 일반 텍스트 로그와의 결정적 차이입니다.

journalctl _PID=1234
journalctl _UID=1000 --since today
journalctl _SYSTEMD_UNIT=nginx.service _TRANSPORT=stdout
journalctl -F _SYSTEMD_UNIT | head -30

-F는 특정 필드에 존재하는 모든 값을 나열합니다. 어떤 유닛이 로그를 남기고 있는지 한눈에 파악할 때 씁니다.


3. 저널 보존 정책 — 디스크를 지키는 설정

journald의 기본 동작은 배포판마다 다릅니다. 가장 큰 차이는 저널이 디스크에 영구 저장되는가입니다.

journalctl --disk-usage
ls -ld /var/log/journal /run/log/journal 2>/dev/null

/var/log/journal 디렉터리가 존재하면 영구 저장(persistent), 없고 /run/log/journal만 있으면 휘발성(volatile)입니다. 휘발성이면 재부팅 시 이전 로그가 전부 사라집니다. 서버가 갑자기 재부팅됐을 때 원인을 못 찾는 흔한 이유입니다.

설정은 /etc/systemd/journald.conf 또는 /etc/systemd/journald.conf.d/ 아래 조각 파일에 둡니다.

[Journal]
Storage=persistent
Compress=yes
SystemMaxUse=2G
SystemKeepFree=1G
SystemMaxFileSize=128M
MaxRetentionSec=30day
MaxFileSec=1day
RateLimitIntervalSec=30s
RateLimitBurst=10000
ForwardToSyslog=yes

각 항목의 의미를 짚습니다.

  • Storage=persistent/var/log/journal을 만들고 영구 저장합니다.
  • SystemMaxUse는 저널 전체가 쓸 최대 용량, SystemKeepFree는 남겨 둘 여유 공간입니다. 두 조건 중 더 엄격한 쪽이 적용됩니다.
  • MaxRetentionSec은 보존 기간입니다. 용량 조건과 함께 걸어 두는 편이 안전합니다.
  • RateLimitIntervalSecRateLimitBurst는 폭주하는 서비스가 디스크를 채우는 것을 막습니다. 한 서비스가 로그를 쏟아부어 다른 서비스의 로그를 밀어내는 사고를 방지하는 핵심 설정입니다.

적용은 재적재로 합니다.

sudo systemctl restart systemd-journald
journalctl --disk-usage

이미 커진 저널을 즉시 줄이려면 vacuum 계열 명령을 씁니다.

sudo journalctl --vacuum-size=500M
sudo journalctl --vacuum-time=7d
sudo journalctl --vacuum-files=5

파괴적 명령 경고: vacuum 명령은 보관된 저널 파일을 삭제합니다. 조사 중인 사건의 기록이 그 범위에 들어 있으면 함께 사라지므로, 디스크가 급한 상황이라도 필요한 구간을 먼저 파일로 뽑아 두세요.

journalctl --since '2026-08-15 00:00' -o export > /backup/journal-20260815.export

저널 무결성 검사도 가능합니다.

journalctl --verify

4. rsyslog — 파일과 원격으로 내보내기

저널만으로 충분한 환경도 있지만, 파일 기반 도구와 연동하거나 원격 수집기로 보내려면 rsyslog가 필요합니다.

설정 구조는 세 부분입니다. 모듈 적재, 규칙, 템플릿입니다. 최신 문법(RainerScript)을 쓰는 편이 읽기 좋습니다.

module(load="imuxsock")
module(load="imklog")

template(name="DetailedFormat" type="string"
  string="%TIMESTAMP:::date-rfc3339% %HOSTNAME% %syslogtag%%msg:::sp-if-no-1st-sp%%msg:::drop-last-lf%\n")

if $programname == 'myapp' then {
  action(type="omfile" file="/var/log/myapp/app.log" template="DetailedFormat")
  stop
}

*.info;mail.none;authpriv.none;cron.none  /var/log/messages
authpriv.*                                /var/log/secure

기본 로그 파일 경로가 배포판마다 다릅니다. RHEL 계열은 /var/log/messages/var/log/secure, Debian/Ubuntu 계열은 /var/log/syslog/var/log/auth.log 입니다. 문서나 스크립트를 옮길 때 가장 자주 깨지는 부분입니다.

원격 전송은 TCP를 쓰고 큐를 걸어야 합니다.

action(type="omfwd"
  target="logs.example.com" port="514" protocol="tcp"
  queue.type="linkedlist"
  queue.filename="fwdRule1"
  queue.maxdiskspace="1g"
  queue.saveonshutdown="on"
  action.resumeRetryCount="-1")

queue.filename을 지정하면 메모리 큐가 가득 찰 때 디스크로 넘깁니다. action.resumeRetryCount="-1"은 무한 재시도입니다. 수집 서버가 잠깐 죽어도 로그를 잃지 않게 하는 최소 구성이며, 대신 디스크 여유를 반드시 확인해야 합니다. 큐 파일이 무한정 커지면 그 자체가 장애가 됩니다.

설정 검증과 적용은 다음과 같습니다.

sudo rsyslogd -N1
sudo systemctl reload rsyslog
logger -p local0.info 'test message from operator'
sudo tail -5 /var/log/messages

rsyslogd -N1은 설정 문법만 검사합니다. 적용 전에 반드시 실행하세요. 문법 오류가 있으면 rsyslog가 뜨지 않고, 그동안의 로그가 통째로 사라집니다.


5. logrotate — 파일이 무한히 자라지 않게

logrotate는 주기적으로 실행되어 조건에 맞는 파일을 회전시킵니다. 설정은 /etc/logrotate.conf/etc/logrotate.d/ 아래에 둡니다.

/var/log/myapp/*.log {
    daily
    rotate 14
    maxsize 100M
    missingok
    notifempty
    compress
    delaycompress
    create 0640 myapp myapp
    su myapp myapp
    sharedscripts
    postrotate
        /bin/kill -USR1 $(cat /run/myapp/myapp.pid 2>/dev/null) 2>/dev/null || true
    endscript
}

각 지시어의 문서상 의미입니다.

  • rotate 14: 14번 회전할 때까지 보관하고 그 이후 삭제합니다.
  • daily: 하루에 한 번 회전합니다. weekly, monthly도 있습니다.
  • size: 지정 크기보다 커졌을 때만 회전합니다. 시간 조건을 무시합니다.
  • maxsize: 시간 조건과 무관하게 크기를 넘으면 회전합니다.
  • minsize: 크기를 넘어야 하되 시간 조건도 만족해야 합니다.
  • compress: 기본적으로 gzip으로 압축합니다.
  • delaycompress: 압축을 한 주기 미룹니다. 아직 파일을 잡고 있는 프로세스가 있을 때 필요합니다.
  • missingok: 파일이 없어도 오류를 내지 않고 넘어갑니다.
  • notifempty: 비어 있으면 회전하지 않습니다.
  • create 모드 소유자 그룹: 회전 직후 같은 이름의 새 파일을 지정한 권한으로 만듭니다.
  • su 사용자 그룹: 지정한 사용자·그룹 권한으로 회전을 수행합니다.
  • sharedscripts: 와일드카드로 여러 파일이 매칭되어도 스크립트를 한 번만 실행합니다.
  • dateext: 회전 파일 이름에 날짜를 붙입니다.
  • olddir: 회전된 파일을 다른 디렉터리로 옮깁니다.

가장 중요한 선택은 createcopytruncate 중 무엇을 쓰는가입니다.

방식동작위험
create(기본)원본을 이름만 바꾸고 새 파일을 만듦프로세스가 재오픈하지 않으면 옛 파일에 계속 씀
copytruncate복사한 뒤 원본을 0으로 자름복사와 자르기 사이의 로그가 유실될 수 있음

create 방식에서는 postrotate 훅으로 프로세스에 재오픈을 알려야 합니다. nginx는 USR1, 많은 데몬은 HUP을 관례로 씁니다. 이 신호를 보내지 않으면 프로세스는 이름이 바뀐 옛 파일에 계속 쓰고, 그 파일은 df에서 사라지지 않는 유령 용량이 됩니다.

copytruncate는 재오픈을 시킬 수 없는 프로그램에 대한 차선책입니다. 로그 유실 가능성을 감수하는 선택이라는 점을 문서화해 두세요.

검증은 반드시 dry run으로 합니다.

sudo logrotate --debug /etc/logrotate.d/myapp
sudo logrotate -d /etc/logrotate.conf
sudo logrotate -f /etc/logrotate.d/myapp
cat /var/lib/logrotate.status

--debug(-d)는 문서에 따르면 아무 변경도 하지 않고 상태 파일도 갱신하지 않습니다. 안전하게 시뮬레이션할 수 있습니다. -f는 조건과 무관하게 강제 회전이며, 실제로 파일을 바꾸므로 신중하게 쓰세요.

상태 파일 기본 위치는 /var/lib/logrotate.status입니다. 배포판에 따라 경로가 다를 수 있으니 실제 경로는 유닛 파일이나 cron 스크립트에서 확인하세요.


6. 무엇을 얼마나 남길 것인가

기술 설정보다 어려운 것은 정책입니다. 판단 기준을 세 축으로 나눠 정리합니다.

축 1 — 조사 가능성. 장애 원인을 찾으려면 최소 며칠이 필요한가. 주말에 발생해 월요일에 인지되는 문제를 고려하면 최소 7일, 실무적으로는 14일이 하한선입니다.

축 2 — 규제와 감사. 인증 로그와 권한 변경 로그는 보존 기간이 법이나 내부 규정으로 정해져 있는 경우가 많습니다. 이 로그는 다른 로그와 분리해 별도 정책을 적용하세요.

축 3 — 비용. 디스크는 유한합니다. 원본을 오래 두기보다 원본은 짧게, 중앙 수집은 길게 가져가는 구성이 일반적입니다.

이 세 축을 반영한 계층 구성이 실무 표준에 가깝습니다.

계층보존목적
호스트 저널7-14일즉시 조사, 재부팅 원인 추적
호스트 파일14-30일파일 기반 도구 연동
중앙 수집90일교차 조사, 대시보드
감사 아카이브1년 이상규제 대응

용량 산정은 실측이 답입니다. 며칠 돌려 보고 계산하세요.

journalctl --disk-usage
du -sh /var/log
du -x -h --max-depth=1 /var/log | sort -h | tail -10

계산은 단순합니다. 하루 로그량에 보존 일수를 곱하고 압축률을 감안한 뒤, 여기에 여유분을 더합니다. 압축은 텍스트 로그에서 대체로 크게 효과를 보지만 이미 압축된 데이터나 이진 페이로드가 섞이면 효과가 떨어지므로, 추정치보다 실측치를 우선하세요. 그리고 평상시가 아니라 장애 시의 로그량으로 산정해야 합니다. 장애가 나면 오류 로그가 평소의 수십 배로 늘어나고, 그때 디스크가 차면 조사에 필요한 로그마저 잃습니다.

로그가 디스크를 채워 서비스가 죽는 사고는 지금도 가장 흔한 장애 유형 중 하나입니다. 방어선을 두 겹으로 두세요. 첫째는 journald의 SystemMaxUse와 logrotate의 maxsize, 둘째는 /var/log를 별도 파일시스템으로 분리해 루트 파일시스템까지 채우지 않게 하는 것입니다. 디스크 용량 문제의 진단 순서는 리눅스 장애 대응 명령어 가이드를 참고하세요.


7. 자주 겪는 함정 다섯 가지

함정 1 — 로그를 지웠는데 용량이 안 줄어듭니다. 프로세스가 파일을 잡고 있습니다.

sudo lsof -nP +L1 | head

해결은 삭제가 아니라 재오픈 신호나 재기동입니다. 원리는 파일 디스크립터와 inode 가이드에서 다룹니다.

함정 2 — 같은 메시지가 두 번 기록됩니다. rsyslog가 저널과 소켓을 동시에 읽고 있거나, ForwardToSyslog가 켜진 상태에서 rsyslog도 imjournal을 쓰는 경우입니다. 입력 경로를 하나로 정리하세요.

함정 3 — 시각이 안 맞습니다. 저널은 기본적으로 로컬 시각으로 보여 주지만 파일 로그는 다른 형식일 수 있습니다. 조사할 때는 UTC로 통일하는 편이 혼란이 적습니다.

journalctl --utc --since '2026-08-15 05:00' --until '2026-08-15 06:00'
timedatectl status

함정 4 — 컨테이너 로그가 노드를 채웁니다. 컨테이너 표준 출력은 노드의 파일에 쌓입니다. 컨테이너 런타임 쪽 회전 설정이 별도로 필요하며, 이 설정은 logrotate와 무관하게 동작합니다. 노드 디스크 경고가 반복된다면 여기부터 확인하세요.

함정 5 — 폭주하는 서비스가 다른 로그를 밀어냅니다. journald의 속도 제한을 서비스 단위로 조정할 수 있습니다.

[Service]
LogRateLimitIntervalSec=10s
LogRateLimitBurst=500

이 지시어의 가용 여부는 systemd 버전에 따라 다르므로, 동작하지 않으면 설치된 버전의 systemd.exec 문서에서 확인하세요.


퀴즈: 실력을 확인해 보세요

퀴즈 1: 서버가 새벽에 재부팅됐는데 그 시점의 로그가 하나도 없습니다. 원인은?

정답: 저널이 휘발성 모드였을 가능성이 큽니다

설명: /var/log/journal 디렉터리가 없으면 저널은 /run/log/journal에만 쓰이고 재부팅 시 사라집니다.

ls -ld /var/log/journal /run/log/journal
journalctl --list-boots

--list-boots에 이전 부팅이 보이지 않으면 확정입니다. 조치는 영구 저장 활성화입니다.

[Journal]
Storage=persistent
SystemMaxUse=2G

설정 후 journald를 재기동하면 다음 부팅부터 기록이 남습니다. 이번 사건의 로그는 되살릴 수 없습니다.

퀴즈 2: logrotate 설정을 바꿨습니다. 실제 파일을 건드리지 않고 확인하려면?

정답: --debug(-d) 옵션으로 시뮬레이션합니다

설명: 문서에 따르면 --debug는 로그에 아무 변경도 하지 않고 상태 파일도 갱신하지 않습니다.

sudo logrotate --debug /etc/logrotate.d/myapp

출력에서 어떤 파일이 어떤 조건으로 회전 대상이 되는지, postrotate 스크립트가 언제 실행될지 확인할 수 있습니다. -f는 강제 회전으로 실제 변경이 일어나므로 검증 목적에는 부적합합니다.

퀴즈 3: 로그 파일을 회전시켰는데 애플리케이션이 계속 옛 파일에 씁니다. 무엇이 빠졌나요?

정답: 회전 후 프로세스에 파일 재오픈을 알리는 postrotate 훅이 없습니다

설명: create 방식은 파일 이름만 바꿉니다. 프로세스가 잡고 있는 파일 디스크립터는 이름이 아니라 inode를 가리키므로, 재오픈하지 않으면 이름이 바뀐 옛 파일에 계속 씁니다.

postrotate
    /bin/kill -USR1 $(cat /run/myapp/myapp.pid 2>/dev/null) 2>/dev/null || true
endscript

재오픈을 시킬 수 없는 프로그램이라면 copytruncate가 대안이지만, 복사와 자르기 사이의 로그가 유실될 수 있다는 점을 감수해야 합니다.

퀴즈 4: 디스크가 가득 찼습니다. 저널을 즉시 줄이되 조사에 필요한 구간은 지키려면?

정답: 필요한 구간을 먼저 파일로 내보낸 뒤 vacuum을 실행합니다

설명: vacuum 명령은 보관된 저널 파일을 삭제하므로 순서가 중요합니다.

journalctl --since '2026-08-15 00:00' --until '2026-08-15 12:00' -o export > /backup/journal-incident.export
sudo journalctl --vacuum-size=500M
journalctl --disk-usage

근본 대책은 사후 삭제가 아니라 상한 설정입니다.

[Journal]
SystemMaxUse=2G
SystemKeepFree=1G
MaxRetentionSec=14day
퀴즈 5: 원격 로그 수집 서버가 30분간 다운됐습니다. 그동안의 로그를 잃지 않으려면 무엇이 설정되어 있어야 하나요?

정답: rsyslog 액션에 디스크 지원 큐와 재시도 설정이 있어야 합니다

설명: 기본 메모리 큐는 용량이 작아 금방 넘칩니다.

action(type="omfwd"
  target="logs.example.com" port="514" protocol="tcp"
  queue.type="linkedlist"
  queue.filename="fwdRule1"
  queue.maxdiskspace="1g"
  queue.saveonshutdown="on"
  action.resumeRetryCount="-1")

queue.saveonshutdown은 재기동 시에도 큐를 보존합니다. 다만 큐 파일이 커지면 그 자체가 디스크 문제가 되므로 queue.maxdiskspace를 반드시 함께 지정하세요.

퀴즈 6: 특정 유닛의 로그만, 표준 출력으로 나온 것만 보고 싶습니다. 어떻게 조회하나요?

정답: 저널의 구조화 필드를 조합해 필터합니다

설명: 저널은 텍스트가 아니라 필드를 저장하므로 정밀한 필터가 가능합니다.

journalctl _SYSTEMD_UNIT=nginx.service _TRANSPORT=stdout
journalctl -u nginx.service -o json-pretty -n 1
journalctl -F _TRANSPORT

-F로 해당 필드에 존재하는 값 목록을 먼저 확인하면 어떤 값을 넣어야 하는지 알 수 있습니다. 이런 조회는 일반 텍스트 로그로는 흉내 내기 어렵고, 저널을 쓰는 실질적인 이유 중 하나입니다.


마치며

로그 배관은 한 번 설계해 두면 몇 년을 갑니다. 그리고 설계하지 않으면 몇 년 동안 조용히 문제를 쌓습니다. 새 서버를 만들 때 다음 다섯 가지만 확인하면 대부분의 사고를 예방할 수 있습니다.

저널이 영구 저장인가. 저널에 용량 상한이 있는가. 로그 파일에 회전 정책이 있는가. 회전 후 재오픈 신호가 가는가. /var/log가 루트 파일시스템을 채울 수 있는 구조인가.

이 다섯 줄을 서버 구축 체크리스트에 넣으세요. 한 번 넣어 두면 다시 생각할 필요가 없어집니다. 남은 시간은 로그의 내용을 좋게 만드는 데 쓰는 편이 훨씬 가치 있습니다.


참고 자료


이어서 읽기

The complete guide to Linux log operations: tying journald, rsyslog, and logrotate into one system

Introduction

Failures caused by logs wear two faces. Either the log is missing when you need it, or it fills the disk when you do not. Both are the result of running the default plumbing without ever designing it.

This blog has designing structured logging, and that post deals with what an application should record and in which fields. This post is the layer below it. The subject is where the logs an application emits flow on the host, how much is kept, and when it gets deleted. Not the content of the log, but its plumbing.

The scope covers three layers. systemd-journald collects, rsyslog sends to files and to remote destinations, and logrotate rotates the files. The baseline is systemd 250 or newer, rsyslog 8.x, and logrotate 3.18 or newer; differences in paths between distributions are noted as they come up.


1. The path a log travels

On a modern Linux system the starting point for logs is journald in most cases. There are four inputs.

  • The kernel ring buffer (/dev/kmsg)
  • The syslog socket (/dev/log)
  • The native journal API
  • The standard output and standard error of services

That last item matters. Everything a process running as a systemd unit prints to the screen goes into the journal automatically. It means logs are recorded even when the application never writes to a file itself, which also lines up with standard practice in the container era.

There are two paths out of the journal to the next stage.

systemctl status systemd-journald.service
systemctl status rsyslog.service
ls -l /run/systemd/journal/

rsyslog normally either reads the journal through the imjournal module or takes the socket directly with imuxsock. Turning both on at the same time records the same message twice, so you must use only one. Start by checking what your distribution defaults to.

grep -rn 'imjournal\|imuxsock' /etc/rsyslog.conf /etc/rsyslog.d/ 2>/dev/null

The first thing to settle when designing the plumbing is which side is the single source of truth. There are three options. Treat the journal as the original and files as secondary; treat files as the original and keep the journal to a minimum; or keep both and ship everything to a central collector. The third is the most common and also carries the most duplication.

The deciding factor is tooling. If the investigation tools your team uses are centred on journalctl, the journal is the natural original; if you have many file-reading agents or existing scripts, files are the natural original. What matters is writing down which one you picked. If that is not settled, logs pile up in two places in two different formats, and every investigation adds time spent working out which side is correct.


2. journalctl — learn how to query first

Before you change any settings, you should be fluent at querying. Fast queries mean shorter outages.

journalctl -u nginx.service -n 200 --no-pager
journalctl -u nginx.service -f
journalctl -p err -b
journalctl -k -b -1
journalctl --since '2026-08-15 14:00' --until '2026-08-15 14:30'
journalctl -g 'timeout|refused' --since today
journalctl -o json-pretty -n 1
journalctl --list-boots
journalctl --user-unit myapp.service

The precise meaning of each option is as follows.

  • -u filters by unit or by pattern. --user-unit is for user session units.
  • -p is the priority filter and accepts both names and numbers. The order is emerg(0), alert(1), crit(2), err(3), warning(4), notice(5), info(6), debug(7).
  • -b queries by boot. -b -1 is the previous boot.
  • -k shows kernel messages only.
  • -g applies a regular expression to the MESSAGE field.
  • -o is the output format and supports short, short-iso, verbose, json, json-pretty, cat, and others.
  • -x appends the explanation from the message catalog. Useful when you hit an unfamiliar error.

The journal holds structured fields, so you can query field by field. That is the decisive difference from a plain text log.

journalctl _PID=1234
journalctl _UID=1000 --since today
journalctl _SYSTEMD_UNIT=nginx.service _TRANSPORT=stdout
journalctl -F _SYSTEMD_UNIT | head -30

-F lists every value present in a given field. You use it to see at a glance which units are producing logs.


3. Journal retention policy — the settings that protect the disk

The default behaviour of journald differs between distributions. The biggest difference is whether the journal is stored persistently on disk.

journalctl --disk-usage
ls -ld /var/log/journal /run/log/journal 2>/dev/null

If the /var/log/journal directory exists the storage is persistent; if it does not and only /run/log/journal is there, storage is volatile. When it is volatile, every earlier log disappears on reboot. That is the common reason nobody can find the cause after a server reboots unexpectedly.

The configuration lives in /etc/systemd/journald.conf or in fragment files under /etc/systemd/journald.conf.d/.

[Journal]
Storage=persistent
Compress=yes
SystemMaxUse=2G
SystemKeepFree=1G
SystemMaxFileSize=128M
MaxRetentionSec=30day
MaxFileSec=1day
RateLimitIntervalSec=30s
RateLimitBurst=10000
ForwardToSyslog=yes

What each entry means.

  • Storage=persistent creates /var/log/journal and stores persistently.
  • SystemMaxUse is the maximum space the journal as a whole may consume; SystemKeepFree is the free space to leave behind. Whichever of the two conditions is stricter is the one that applies.
  • MaxRetentionSec is the retention period. Setting it alongside the size condition is the safer option.
  • RateLimitIntervalSec and RateLimitBurst stop a runaway service from filling the disk. This is the key setting that prevents one service flooding the log and pushing out the logs of every other service.

Apply it with a restart.

sudo systemctl restart systemd-journald
journalctl --disk-usage

To shrink a journal that has already grown large, use the vacuum family of commands.

sudo journalctl --vacuum-size=500M
sudo journalctl --vacuum-time=7d
sudo journalctl --vacuum-files=5

Destructive command warning: the vacuum commands delete archived journal files. If the records of the incident you are investigating fall inside that range, they go too, so even when the disk is urgent, export the range you need to a file first.

journalctl --since '2026-08-15 00:00' -o export > /backup/journal-20260815.export

You can also verify journal integrity.

journalctl --verify

4. rsyslog — exporting to files and to remote destinations

There are environments where the journal alone is enough, but if you need to integrate with file-based tooling or ship to a remote collector, you need rsyslog.

The configuration structure has three parts: loading modules, rules, and templates. The modern syntax (RainerScript) reads better.

module(load="imuxsock")
module(load="imklog")

template(name="DetailedFormat" type="string"
  string="%TIMESTAMP:::date-rfc3339% %HOSTNAME% %syslogtag%%msg:::sp-if-no-1st-sp%%msg:::drop-last-lf%\n")

if $programname == 'myapp' then {
  action(type="omfile" file="/var/log/myapp/app.log" template="DetailedFormat")
  stop
}

*.info;mail.none;authpriv.none;cron.none  /var/log/messages
authpriv.*                                /var/log/secure

The default log file paths differ between distributions. RHEL-family systems use /var/log/messages and /var/log/secure; Debian/Ubuntu-family systems use /var/log/syslog and /var/log/auth.log. This is the part that breaks most often when documents or scripts are moved between systems.

Remote forwarding should use TCP and should have a queue in front of it.

action(type="omfwd"
  target="logs.example.com" port="514" protocol="tcp"
  queue.type="linkedlist"
  queue.filename="fwdRule1"
  queue.maxdiskspace="1g"
  queue.saveonshutdown="on"
  action.resumeRetryCount="-1")

Specifying queue.filename lets the queue spill to disk when the memory queue fills. action.resumeRetryCount="-1" means retry indefinitely. This is the minimum configuration that keeps you from losing logs when the collection server dies briefly, and in exchange you have to keep an eye on free disk space. A queue file that grows without limit becomes an incident in its own right.

Validating and applying the configuration goes like this.

sudo rsyslogd -N1
sudo systemctl reload rsyslog
logger -p local0.info 'test message from operator'
sudo tail -5 /var/log/messages

rsyslogd -N1 checks configuration syntax only. Run it before you apply anything, every time. If there is a syntax error, rsyslog does not come up, and every log produced in the meantime is gone.


5. logrotate — keeping files from growing forever

logrotate runs periodically and rotates the files that match its conditions. The configuration lives in /etc/logrotate.conf and under /etc/logrotate.d/.

/var/log/myapp/*.log {
    daily
    rotate 14
    maxsize 100M
    missingok
    notifempty
    compress
    delaycompress
    create 0640 myapp myapp
    su myapp myapp
    sharedscripts
    postrotate
        /bin/kill -USR1 $(cat /run/myapp/myapp.pid 2>/dev/null) 2>/dev/null || true
    endscript
}

The documented meaning of each directive.

  • rotate 14: keep files through 14 rotations, then delete.
  • daily: rotate once a day. weekly and monthly also exist.
  • size: rotate only when the file has grown past the given size. It ignores the time condition.
  • maxsize: rotate when the size is exceeded regardless of the time condition.
  • minsize: the size must be exceeded, but the time condition must also be met.
  • compress: compress with gzip by default.
  • delaycompress: defer compression by one cycle. Needed when a process is still holding the file.
  • missingok: do not raise an error if the file is absent, just move on.
  • notifempty: do not rotate if the file is empty.
  • create mode owner group: right after rotation, create a new file with the same name and the given permissions.
  • su user group: perform the rotation with the permissions of the given user and group.
  • sharedscripts: run the scripts only once even when a wildcard matches several files.
  • dateext: append a date to the rotated file name.
  • olddir: move rotated files into a different directory.

The most important choice is whether you use create or copytruncate.

ModeBehaviourRisk
create (default)Renames the original and creates a new fileIf the process does not reopen, it keeps writing to the old file
copytruncateCopies, then truncates the original to zeroLogs written between the copy and the truncate can be lost

With the create mode you have to tell the process to reopen through a postrotate hook. nginx conventionally uses USR1 and many daemons use HUP. If you do not send that signal, the process keeps writing to the renamed old file, and that file becomes phantom capacity that never disappears from df.

copytruncate is the fallback for a program you cannot make reopen its files. Document the fact that it is a choice that accepts the possibility of losing logs.

Always validate with a dry run.

sudo logrotate --debug /etc/logrotate.d/myapp
sudo logrotate -d /etc/logrotate.conf
sudo logrotate -f /etc/logrotate.d/myapp
cat /var/lib/logrotate.status

According to the documentation, --debug (-d) makes no changes at all and does not update the state file either. You can simulate safely with it. -f forces rotation regardless of the conditions and does change files, so use it carefully.

The default location of the state file is /var/lib/logrotate.status. The path can differ by distribution, so check the actual path in the unit file or the cron script.


6. What to keep and how much of it

Policy is harder than the technical settings. Here are the criteria, split along three axes.

Axis 1 — investigability. How many days at minimum do you need to find the cause of a failure. Allowing for a problem that happens over the weekend and is noticed on Monday, seven days is the floor and fourteen is the practical minimum.

Axis 2 — regulation and audit. Authentication logs and privilege change logs frequently have a retention period fixed by law or by internal policy. Separate those logs from the rest and apply their own policy.

Axis 3 — cost. Disk is finite. Rather than keeping the original for a long time, the usual arrangement is short on the original, long in central collection.

A tiered arrangement that reflects those three axes is close to the industry standard.

TierRetentionPurpose
Host journal7-14 daysImmediate investigation, tracing reboots
Host files14-30 daysIntegration with file-based tooling
Central collector90 daysCross-host investigation, dashboards
Audit archive1 year plusRegulatory response

Sizing capacity is a matter of measurement. Run it for a few days and then calculate.

journalctl --disk-usage
du -sh /var/log
du -x -h --max-depth=1 /var/log | sort -h | tail -10

The arithmetic is simple. Multiply the daily log volume by the number of retention days, allow for the compression ratio, and add headroom on top. Compression generally does a lot for text logs, but the benefit drops once already-compressed data or binary payloads are mixed in, so trust measurements over estimates. And size it for the log volume during an incident, not during normal operation. When something breaks, error logs grow by tens of times, and if the disk fills at that moment you lose the very logs the investigation needs.

An incident where logs fill the disk and take the service down is still one of the most common failure types. Put two lines of defence in place. The first is SystemMaxUse in journald and maxsize in logrotate; the second is separating /var/log onto its own filesystem so it cannot fill the root filesystem. For the diagnostic order on disk capacity problems, see the Linux incident response command guide.


7. Five traps you will hit often

Trap 1 — you deleted the logs and the space did not come back. A process is still holding the file.

sudo lsof -nP +L1 | head

The fix is not deletion but a reopen signal or a restart. The underlying mechanism is covered in the guide to file descriptors and inodes.

Trap 2 — the same message is recorded twice. Either rsyslog is reading the journal and the socket at the same time, or ForwardToSyslog is on while rsyslog is also using imjournal. Consolidate down to one input path.

Trap 3 — the timestamps do not line up. The journal shows local time by default, while file logs may be in a different format. When investigating, standardising on UTC causes less confusion.

journalctl --utc --since '2026-08-15 05:00' --until '2026-08-15 06:00'
timedatectl status

Trap 4 — container logs fill the node. Container standard output accumulates in files on the node. Rotation has to be configured separately on the container runtime side, and that configuration works independently of logrotate. If node disk warnings keep repeating, start here.

Trap 5 — a runaway service pushes out other logs. The journald rate limit can be adjusted per service.

[Service]
LogRateLimitIntervalSec=10s
LogRateLimitBurst=500

Whether these directives are available depends on the systemd version, so if they do not work, check the systemd.exec documentation for the version you have installed.


Quiz: check your understanding

Quiz 1: The server rebooted overnight and there is not a single log from that moment. What is the cause?

Answer: Most likely the journal was in volatile mode

Why: If the /var/log/journal directory does not exist, the journal is written only to /run/log/journal and disappears on reboot.

ls -ld /var/log/journal /run/log/journal
journalctl --list-boots

If --list-boots shows no earlier boot, that confirms it. The fix is to enable persistent storage.

[Journal]
Storage=persistent
SystemMaxUse=2G

After the change, restarting journald means records survive from the next boot onward. The logs from this particular event cannot be brought back.

Quiz 2: You changed a logrotate configuration. How do you check it without touching the actual files?

Answer: Simulate it with the --debug (-d) option

Why: According to the documentation, --debug makes no changes to the logs and does not update the state file either.

sudo logrotate --debug /etc/logrotate.d/myapp

The output tells you which files become rotation candidates under which condition, and when the postrotate script would run. -f is a forced rotation that makes real changes, so it is not suitable for validation.

Quiz 3: You rotated the log file but the application keeps writing to the old one. What is missing?

Answer: A postrotate hook telling the process to reopen the file after rotation

Why: The create mode only changes the file name. The file descriptor a process holds points at an inode rather than a name, so unless it reopens, it keeps writing to the renamed old file.

postrotate
    /bin/kill -USR1 $(cat /run/myapp/myapp.pid 2>/dev/null) 2>/dev/null || true
endscript

If the program cannot be made to reopen, copytruncate is the alternative, but you have to accept that logs written between the copy and the truncate can be lost.

Quiz 4: The disk is full. How do you shrink the journal immediately while protecting the range you need for the investigation?

Answer: Export the range you need to a file first, then run vacuum

Why: The vacuum commands delete archived journal files, so the order matters.

journalctl --since '2026-08-15 00:00' --until '2026-08-15 12:00' -o export > /backup/journal-incident.export
sudo journalctl --vacuum-size=500M
journalctl --disk-usage

The real fix is setting ceilings rather than deleting after the fact.

[Journal]
SystemMaxUse=2G
SystemKeepFree=1G
MaxRetentionSec=14day
Quiz 5: The remote log collection server was down for 30 minutes. What has to be configured so that you do not lose the logs from that window?

Answer: The rsyslog action needs a disk-assisted queue and retry settings

Why: The default memory queue is small and overflows quickly.

action(type="omfwd"
  target="logs.example.com" port="514" protocol="tcp"
  queue.type="linkedlist"
  queue.filename="fwdRule1"
  queue.maxdiskspace="1g"
  queue.saveonshutdown="on"
  action.resumeRetryCount="-1")

queue.saveonshutdown preserves the queue across a restart. But a queue file that grows large becomes a disk problem in itself, so always specify queue.maxdiskspace alongside it.

Quiz 6: You want only the logs of one specific unit, and only the ones that came from standard output. How do you query that?

Answer: Combine the structured fields of the journal in the filter

Why: The journal stores fields rather than text, which makes precise filtering possible.

journalctl _SYSTEMD_UNIT=nginx.service _TRANSPORT=stdout
journalctl -u nginx.service -o json-pretty -n 1
journalctl -F _TRANSPORT

Checking the list of values present in a field with -F first tells you what to put in. Queries like this are hard to imitate with plain text logs, and they are one of the substantive reasons to use the journal.


Closing

Log plumbing, designed once, lasts for years. And if you never design it, it quietly accumulates problems for years. Checking just these five things when you build a new server prevents most incidents.

Is the journal persistent. Does the journal have a size ceiling. Do the log files have a rotation policy. Does a reopen signal go out after rotation. Is /var/log able to fill the root filesystem.

Put those five lines in your server build checklist. Once they are in, you never have to think about them again. The time you get back is far more valuable spent on making the content of the logs good.


References


Further reading