logrotate on Linux: Configuration, Testing, and Common Pitfalls
Updated July 27, 2026
logrotate -d /etc/logrotate.d/myapp: a dry run that prints what would happen without touching files. The one concept that prevents most logrotate bugs: after renaming a log file, the app keeps writing to the old file handle until it reopens, so every config needs either a postrotate signal or copytruncate.logrotate -d /etc/logrotate.d/myappHow and when logrotate actually runs
logrotate is not a daemon. It is a one-shot program run on a schedule, via the systemd logrotate.timer on modern distributions or /etc/cron.daily/logrotate on older ones. It records when each log was last rotated in a state file (/var/lib/logrotate/status on RHEL-family, /var/lib/logrotate/logrotate.status on Debian-family) and only rotates when the configured schedule or size condition is due.
systemctl list-timers logrotate.timer
systemctl status logrotate.service
cat /var/lib/logrotate/status 2>/dev/null || cat /var/lib/logrotate/logrotate.statusStep 1: write the config
Each block names a glob of log files and the directives that apply. The workhorse directives: daily/weekly/monthly for cadence, rotate N for how many archives to keep, maxsize to rotate early when a file blows past a threshold (size alone ignores the time schedule entirely), compress with delaycompress (keep the newest archive uncompressed since the app may still write to it briefly), missingok and notifempty to avoid noise, and create mode owner group to make the fresh file with correct permissions.
/var/log/myapp/*.log {
daily
rotate 14
maxsize 100M
compress
delaycompress
missingok
notifempty
create 0640 myapp myapp
sharedscripts
postrotate
systemctl kill -s USR1 myapp.service 2>/dev/null || true
endscript
}Step 2: handle the reopen problem
Renaming a file does not affect the file handle the app holds: after rotation, the app happily keeps writing into myapp.log.1. Two solutions. Preferred: a postrotate script that tells the app to reopen its logs (many daemons reopen on SIGUSR1 or SIGHUP; nginx uses USR1, and systemctl reload works for services that support it). Fallback: copytruncate, which copies the file and truncates the original in place: no app cooperation needed, but lines written between the copy and the truncate are lost, and the copy briefly doubles disk usage.
# preferred — app reopens its own logs:
postrotate
systemctl kill -s USR1 nginx.service
endscript
# fallback — no signal needed, small data-loss window:
copytruncateStep 3: test with -d, force with -f
-d is a dry run: it parses the config and narrates every decision ("log does not need rotating") without changing anything: run it after every edit. -f forces a rotation now regardless of schedule, the fastest way to verify the full cycle including postrotate scripts. -v adds verbosity to a real run.
logrotate -d /etc/logrotate.d/myapp # dry run, safe
sudo logrotate -f /etc/logrotate.d/myapp # force a real rotation
sudo logrotate -v /etc/logrotate.conf # verbose full run, all configsStep 4: verify it keeps working
After the config lands, confirm three things over the next cycle: archives appear (myapp.log.1, then .2.gz with delaycompress), the live file shrank, and the app is still writing to the new file rather than a rotated one (lsof shows which file the process actually holds open).
ls -lh /var/log/myapp/
lsof +D /var/log/myapp/ # which file is the app really writing to?
grep myapp /var/lib/logrotate/status 2>/dev/nullCommon pitfalls
The classics: "parent directory has insecure permissions": logrotate refuses world/group-writable log directories unless you add the su user group directive. A glob like *.log* matching already-rotated files and re-rotating them: keep globs tight. Using size with a daily cron and expecting hourly behavior: logrotate only evaluates conditions when it runs, so a file can massively exceed maxsize between daily runs. And postrotate running once per matched file when you wanted once total. That is what sharedscripts fixes.
Log rotation is really a retention policy: decide what history you keep, for how long, and in what form, because the day you need it is the day after you deleted it. Audit trails for AI agents carry the same obligation with higher stakes: when an agent-made change is questioned months later, the record of who approved what and why has to still exist. Nexus treats its agent audit trail as governed retention, not disposable logs: every action, policy decision, and cost attribution kept queryable for as long as your compliance posture requires.
Explore Swfte NexusCommon questions
- How do I test a logrotate configuration without rotating anything?
- Run logrotate -d <config-file>. Debug mode parses the config and prints exactly what would be done (which files match, whether rotation is due, which scripts would run) without modifying a single file. Follow up with -f once to exercise the real cycle.
- Why is my app still writing to the rotated .log.1 file?
- Because renaming a file does not change the file descriptor the app holds open. The config needs a postrotate script that signals the app to reopen its logs (USR1/HUP/reload), or the copytruncate directive if the app cannot be signaled.
- What is the difference between size and maxsize in logrotate?
- size makes file size the only trigger, ignoring the daily/weekly schedule; maxsize combines with the schedule: rotate on the usual cadence, or earlier if the file passes the threshold. Both are only evaluated when logrotate actually runs, typically once a day.
- Does logrotate handle systemd journal logs?
- No: journald manages its own storage with SystemMaxUse and the journalctl --vacuum-size/--vacuum-time commands. logrotate is for plain log files: app logs in /var/log, nginx/apache access logs, and anything else writing flat files.
- Why did logrotate stop rotating after I moved or recreated a log file?
- Check three things: the state file may hold a recent rotation date (logrotate thinks it already ran: force with -f once), the new file may not match the config glob, or directory permissions changed and logrotate now refuses to act without a su directive. Running logrotate -d shows which case you are in.