logrotate
Installation
SKILL.md
Identity
- Binary:
/usr/sbin/logrotate - Config:
/etc/logrotate.conf - Drop-in dir:
/etc/logrotate.d/(each file rotates one or more applications) - State file:
/var/lib/logrotate/status(tracks last rotation time per log path) - Scheduler: run daily via
/etc/cron.daily/logrotateorlogrotate.timer(systemd) - Distro install:
apt install logrotate/dnf install logrotate
Key Operations
| Operation | Command |
|---|---|
| Test config (dry-run, verbose) | logrotate -d /etc/logrotate.conf |
| Test a specific drop-in config | logrotate -d /etc/logrotate.d/nginx |
| Force rotation now (ignore schedule) | logrotate -f /etc/logrotate.conf |
| Force-rotate a single config | logrotate -f /etc/logrotate.d/myapp |
| Run with verbose output | logrotate -v /etc/logrotate.conf |
| Combine force + verbose | logrotate -vf /etc/logrotate.conf |
| Check state file (last rotation times) | cat /var/lib/logrotate/status |
| Show last rotation for one log | grep myapp /var/lib/logrotate/status |
| Validate config syntax only | logrotate --debug /etc/logrotate.conf |
| Run as a specific user | sudo -u www-data logrotate -f /etc/logrotate.d/myapp |
| Check systemd timer status | systemctl status logrotate.timer |
Expected State
- State file updated daily; each line shows
"<path>" <ISO-date> - Daily cron or systemd timer runs without error output (silent on success)
- Rotated files named
app.log.1(numbered) orapp.log-YYYYMMDD(dateext) - Compressed files have
.gzsuffix whencompressis set
Health Checks
logrotate -d /etc/logrotate.conf 2>&1 | grep -i error— no output means config is validstat /var/lib/logrotate/status— confirm file exists and mtime is recent (within 24–48h)grep -r 'error\|warning' /var/log/syslog | grep logrotate— no recent failures in syslog
Common Failures
| Symptom | Likely cause | Check/Fix |
|---|---|---|
error: error opening /var/log/app.log: Permission denied |
logrotate runs as root but postrotate script or create mode doesn't match log owner | Run ls -la /var/log/app.log; add su <user> <group> directive to config block |
postrotate script failed |
postrotate command exits non-zero (e.g., service not running, wrong path) | Run the postrotate command manually; check $?; add sharedscripts to avoid re-running on each matched file |
unknown option or config parse error |
Typo or unsupported directive in config file | Run logrotate -d /etc/logrotate.d/<file> and read the error line number |
| Rotation not happening despite age | Log path glob doesn't match actual file path | Run logrotate -vd and look for "no matches" or "skipping" messages; fix glob in config |
.gz file is corrupted or missing |
Compress fails because gzip not installed, or disk full | Check which gzip; check df -h; try running logrotate -f and observe stderr |
| Application keeps writing to old inode after rotation | Missing or broken postrotate reload signal | Add postrotate ... endscript with SIGHUP or restart; alternatively use copytruncate |
| Old rotated files not being deleted | rotate count too high, or maxage not set |
Check rotate N value; add maxage 30 to remove files older than 30 days |
stat of /var/lib/logrotate/status failed |
State file missing or wrong path | Create with touch /var/lib/logrotate/status; check if alternate state path is set with -s |
Pain Points
- Applications must reopen their log file descriptor after rotation. Rotation renames the file; a running process keeps writing to the old inode (now
.log.1). Fix:postrotateblock sending SIGHUP (or equivalent) to reload the app, or usecopytruncateas a fallback. copytruncaterisks losing log lines. It copies the current log then truncates the original in two steps. Log lines written between copy and truncate are silently lost. Use SIGHUP-based rotation when the application supports it.delaycompressis required when the application is still writing to.log.1. Without it, logrotate compresses.log.1immediately while the app may still hold it open (especially relevant withpostrotatethat only restarts, not reloads).- Daily rotation runs via cron.daily, not at a precise time. The actual run time depends on when cron.daily fires (commonly 06:25 on Debian systems). Logs are not rotated in real time; a 1 GB log can accumulate between runs.
dateextmakes filenames substantially clearer than numbered suffixes. Numbered rotation (app.log.1,.2) shifts all existing files on each run, making timestamp-based grep harder.dateextwithdateformat -%Y%m%davoids this and prevents name collisions.sharedscriptsis almost always wanted for glob patterns. Without it,postrotateruns once per matched file, potentially sending SIGHUP dozens of times to the same process.
References
See references/ for:
logrotate.conf.annotated— full config with every directive explained, nginx and custom app examplesdocs.md— man pages, upstream documentation, and community resources
Related skills