Log Management - journald, Rotation, and Central Shipping
Status: Active
Last Updated: 2026-08-26
Category: Sysadmin - Operations
Prerequisites: system-admin-basics, loki-logging
Time: 2 hours
Tags: logs, journald, logrotate, rsyslog, retention, forensics
Summary
Covers how Linux systems produce, store, rotate, and ship logs: configuring journald, controlling disk usage with rotation and retention policies, options for shipping logs to a central store, and the quick commands you need during an incident.
๐ฏ What You'll Learn
By the end of this article, you'll be able to:
- โ Configure journald persistence, size caps, and sealing
- โ Write and test logrotate rules for application logs
- โ Choose between rsyslog, Vector, Promtail, and Grafana Alloy for central shipping
- โ Define a defensible retention policy
- โ Run fast forensic queries with journalctl
Table of Contents
- Context / Why This Matters
- journald Configuration
- Rotation with logrotate
- Central Shipping Options
- Retention Policy
- Forensics Quick Reference
Context / Why This Matters
Logs are the first evidence source in every incident and the last thing anyone configures properly. On a default systemd host, /var/log/journal may not even exist โ meaning every reboot destroys your history exactly when you need it to explain a crash. Unbounded logs also fill disks quietly, which takes down services that have nothing to do with logging.
This article pairs with metrics-vs-logs (when to log vs. when to measure) and loki-logging (where shipped logs go). Read system-admin-basics first for the inventory and audit discipline these policies feed into.
Implementation / Core Content
1. journald Configuration
Config lives in /etc/systemd/journald.conf (or drop-ins in /etc/systemd/journald.conf.d/*.conf). The single most important setting is persistence:
# /etc/systemd/journald.conf.d/99-fogserv.conf
[Journal]
Storage=persistent
SystemMaxUse=2G
SystemKeepFree=1G
MaxFileSec=30day
ForwardToSyslog=no
Apply and verify:
sudo mkdir -p /var/log/journal
sudo systemd-tmpfiles --create --prefix /var/log/journal
sudo systemctl restart systemd-journald
journalctl --verify # integrity check
du -sh /var/log/journal # current footprint
Early-boot caveat: journald always starts in volatile mode, even with Storage=persistent, until systemd-journal-flush.service completes. A crash during that window loses messages regardless of configuration. Treat the first ~30 seconds of boot as best-effort and pair journald with console= kernel logging or an out-of-band shipper if early-boot forensics matter.
Key settings explained:
| Setting | Meaning |
|---|---|
Storage=persistent |
Logs survive reboot (stored under /var/log/journal) |
SystemMaxUse=2G |
Hard cap on disk usage; oldest journals vacuumed first |
SystemKeepFree=1G |
Never consume the last 1G of free space |
MaxFileSec=30day |
Time-based retention on top of size-based |
Seal=yes (default) |
Cryptographic sealing โ detect tampering after the fact |
For tamper-evidence on security-relevant hosts, enable forward-secure sealing:
sudo journalctl --setup-keys # generates FSS key pair; keep secret key off-host
2. Rotation with logrotate
journald rotates itself; plain-text application logs do not. Anything an app writes directly to /var/log/<app>.log needs logrotate. Configs live in /etc/logrotate.d/:
# /etc/logrotate.d/myapp
/var/log/myapp/*.log {
daily
rotate 14
maxsize 100M
compress
delaycompress
missingok
notifempty
dateext
copytruncate
}
daily+rotate 14: two weeks of history.maxsize 100M: force rotation early if a file balloons.copytruncate: for apps that can't reopen their log file on signal (SIGHUP). Small window where writes between copy and truncate are lost; preferpostrotate ... killall -HUP myapp ... endrotatewhen the app supports re-opening.- Test without side effects:
sudo logrotate --debug /etc/logrotate.d/myapp, or force a real run:sudo logrotate -f /etc/logrotate.d/myapp.
3. Central Shipping Options
One host's logs are anecdotes; a fleet's logs are a dataset. Pick one shipper per host:
| Tool | Fit | Notes |
|---|---|---|
| rsyslog | Already installed everywhere | RFC5424 forwarding over TCP/TLS; lowest effort |
| Grafana Alloy | Grafana/Loki stack | Successor to Promtail; Promtail reached EOL 2026-03-02 and is now in maintenance mode. New deployments should use Alloy |
| Promtail | Legacy Loki stacks | Still works but no security fixes โ migrate to Alloy on the next host rebuild |
| Vector | High-throughput, transforms in-flight | TOML/VRL config, very efficient; vendor-agnostic |
| journald + systemd-journal-remote | Pure-systemd shops | Push/pull structured journal files |
Promtail EOL (2026-03-02): If any host still ships via Promtail, plan a migration to Grafana Alloy โ the configuration shape is similar, and Alloy unifies metrics, logs, and traces in one agent. See Grafana's Alloy documentation for the conversion.
Minimal Loki example (matches our loki-logging stack):
# /etc/alloy/config.alloy (Promtail-compatible scrape of journald)
local.file_match "logs" {
path_targets = [{ __path__ = "/var/log/myapp/*.log", job = "myapp" }]
}
loki.source.file "app" {
targets = local.file_match.logs.targets
forward_to = [loki.write.default.receiver]
}
loki.write "default" {
endpoint { url = "http://loki.internal:3100/loki/api/v1/push" }
}
rsyslog fallback for hosts without agents:
# /etc/rsyslog.d/60-forward.conf
*.* action(type="omfwd" target="syslog.internal" port="514"
protocol="tcp" Template="RSYSLOG_SyslogProtocol23Format")
Rule of thumb: ship structured copies centrally, but never rely on the pipeline as the only copy. Local journald retention stays in place as a buffer.
4. Retention Policy
Define retention per log class, not globally. A starting policy:
| Class | Examples | Local | Central | Rationale |
|---|---|---|---|---|
| Security/auth | auth.log, sudo, sshd | 30 days | 1 year | Incident forensics window |
| Application | service stdout | 14 days | 90 days | Debugging recent regressions |
| System/kernel | journalctl -k | 30 days | 90 days | Crash correlation |
| Access/audit | nginx access, auditd | 7 days | 180 days | Volume-heavy, cheap to re-ingest |
Document the chosen numbers in this KB entry and enforce them mechanically (SystemMaxUse, logrotate rotate N, Loki's retention_period). An unenforced policy is a wish.
5. Forensics Quick Reference
# Everything since boot, errors only
journalctl -p err -b
# A unit's logs since yesterday, following
journalctl -u myapp.service --since yesterday -f
# Kernel ring + OOM kills
journalctl -k --grep "Out of memory"
# All activity by UID 1000 today
journalctl _UID=1000 --since today
# SSH login attempts in the last hour
journalctl -u ssh --since "-1h" | grep -i accept
# Disk space used by journals, then vacuum manually
journalctl --disk-usage
sudo journalctl --vacuum-size=500M
sudo journalctl --vacuum-time=14d
# Export a time window for offline analysis / ticket attachment
journalctl --since "2026-08-26 08:00" --until "2026-08-26 09:00" > incident.log
# Which boot had the crash?
journalctl --list-boots
Practical Examples
Example 1: Diagnose why a service died overnight
journalctl --list-boots # find the relevant boot
journalctl -u api.service -b -1 --since "23:00" --until "06:00"
# Follow with:
journalctl -k -b -1 --grep oom # was it killed by the kernel?
Expected pattern: app logs stop at T, kernel OOM line at T+0s โ memory limit too low.
Example 2: Prove logrotate works before trusting it
sudo logrotate --debug /etc/logrotate.d/myapp # dry run, prints plan
sudo logrotate -f /etc/logrotate.d/myapp # force one rotation now
ls -lh /var/log/myapp/ # confirm .1 + dateext file exists
Troubleshooting & Common Pitfalls
| Problem | Cause | Fix |
|---|---|---|
| Journal empty after reboot | Storage=auto and /var/log/journal missing |
Create dir, set Storage=persistent, restart journald |
| Disk full from logs | No SystemMaxUse; app logs unrotated |
Set caps; add logrotate rule; vacuum once with --vacuum-size |
Rotated log keeps growing in .1 |
App holds open fd; no HUP/reopen | Use copytruncate or add postrotate SIGHUP |
| Gaps in shipped logs | Shipper crashed silently | Monitor the shipper itself; alert on ingestion lag in Loki |
| Can't read journal as non-root | User not in adm/systemd-journal group | sudo usermod -aG systemd-journal $USER |
Next Steps / Ops Actions
- Apply the journald drop-in and verify persistence on all fogserv.cloud hosts; record completion in the host inventory (system-admin-basics).
- Stand up or verify central collection per loki-logging and alerting per simple-alerts.
- Schedule credential-rotation and audit events with the patterns in scheduler-patterns.
- Review secrets that appear in logs โ redact at the source using guidance from secrets.
Sources & Related Articles
External references consulted:
- https://www.freedesktop.org/software/systemd/man/latest/journald.conf.html
- https://man7.org/linux/man-pages/man8/logrotate.8.html
- https://grafana.com/docs/loki/latest/send-data/alloy/
- https://grafana.com/docs/alloy/latest/ (Promtail successor)
- https://www.cisa.gov/resources-tools/resources/logging-reference-architecture (OMB M-26-14 aligned)
Related knowledge-base articles:
Change Log
2026-08-26
- Initial creation covering journald config, logrotate, central shipping options, retention policy, and forensics commands.
2026-08-26 (Task 7 refresh)
- Added early-boot volatile-mode caveat for journald persistence.
- Flagged Promtail EOL (2026-03-02); marked Grafana Alloy as the recommended successor.
- Linked CISA Logging Reference Architecture (OMB M-26-14, May 2026) for federal-style guidance.