Logrotate recipes — keeping /var/log from eating your disk
Every application that logs to a file under /var/log/ eventually fills the disk if nothing rotates it. Distro packages usually drop a logrotate config for their own logs; your custom application doesn't. This article covers the patterns for both writing new logrotate configs and fixing the common breakages in existing ones.
How logrotate works
Logrotate is a cron/timer-invoked utility that reads configs from /etc/logrotate.conf + /etc/logrotate.d/* and, for each log file matching a config, decides whether to rotate it based on the rules. "Rotating" means: rename the current log (foo.log → foo.log.1), optionally compress old ones, optionally delete ones past retention, and (usually) signal the writing process to reopen its file handle.
Logrotate doesn't run continuously. It runs daily via either /etc/cron.daily/logrotate or logrotate.timer, depending on distro. Between runs, your log can grow as much as it wants — sizing is a "this can't exceed X" cap, not a real-time bound.
The minimal custom config
Drop a file at /etc/logrotate.d/myapp:
/var/log/myapp/*.log {
daily
rotate 14
missingok
notifempty
compress
delaycompress
copytruncate
}
What each directive does:
- daily — rotate every day (alternatives:
weekly,monthly,size 100M,hourlywith hourly cron). - rotate 14 — keep 14 rotated copies. Older ones get deleted.
- missingok — don't error if the log file doesn't exist (handy for apps that only sometimes write).
- notifempty — skip rotation if the log is empty.
- compress — gzip rotated copies.
foo.log.2becomesfoo.log.2.gz. - delaycompress — leave the most recent rotation uncompressed for a day. Helps when an app holds the previous file handle briefly after reopen.
- copytruncate — copy the log to its new name, then truncate the original. Works for apps that don't support reopen on signal.
The reopen-on-signal pattern (preferred when supported)
If your app responds to a signal by reopening its log file (most modern daemons do), copytruncate is suboptimal — it can lose log lines written during the copy. Use postrotate instead:
/var/log/nginx/*.log {
daily
rotate 14
missingok
notifempty
compress
delaycompress
sharedscripts
postrotate
[ -f /var/run/nginx.pid ] && kill -USR1 $(cat /var/run/nginx.pid)
endscript
}
The signal varies by app: nginx wants USR1, postfix wants HUP, your custom Python app may want SIGUSR2 if you wrote it that way. Check the app docs.
Size-based instead of date-based
For apps that log unpredictably (very quiet most days, occasional log floods), date-based rotation can leave you with 9 days of empty logs and one 50 GB monster. Size-based covers it:
/var/log/myapp/*.log {
size 100M
rotate 7
compress
delaycompress
missingok
notifempty
copytruncate
}
Caveat: size rotations only happen when logrotate runs (daily by default). To enforce a real-time cap, set hourly rotation and move logrotate to a systemd hourly timer, or write to a journal/syslog daemon with built-in size limits instead.
The "I forgot to rotate and the disk is full" rescue
You can force a rotation immediately without waiting for cron:
# Dry-run first to see what would happen
logrotate -d /etc/logrotate.d/myapp
# Force a rotation
logrotate -f /etc/logrotate.d/myapp
# If the file is huge and you don't care about its contents
truncate -s 0 /var/log/myapp/huge.log # zeroes the file in place, doesn't break the writing process
Don't rm a log file that an active process has open — the disk space won't free until the process closes its handle (which usually means a restart). The truncate -s 0 trick zeros the file content while leaving the inode alive, so the writing process keeps writing.
Common breakages
- Logrotate says it rotated but the file kept growing. The app didn't reopen its handle — fix the
postrotatescript or switch tocopytruncate. - compress hangs. The rotated file is huge; gzip takes minutes. Add
delaycompressto defer compression by one day, or usecompresscmd /usr/bin/zstdfor faster compression. - Permissions errors after rotation. The new log file gets created with logrotate's umask (typically root's). Use
create 0640 myapp myappto set the expected ownership. - "file already exists" errors mid-rotation — a previous run died part way through and left half-rotated files. Manually clean up
foo.log.1,foo.log.2etc. and re-run.
What logrotate is NOT
It's not a log shipping or aggregation tool. If you need centralized logs across multiple boxes, send to a syslog server (rsyslog forwarding, or a hosted aggregator) and let that handle retention. See the related article on log shipping.
Also Read
Powered by WHMCompleteSolution