Log files have a way of growing quietly until they fill up your disk at the worst possible moment. I’ve seen it happen more than once — a busy web server accumulating logs, nobody checking, and then one night the disk hits 100% and everything stops. Logrotate prevents this.
It’s already installed on most Linux systems and handles log rotation for many system services automatically. But understanding how it works lets you configure it for your own application logs.
How Logrotate Works
Logrotate runs periodically (usually daily via cron or systemd timer) and processes rotation jobs defined in config files. For each job it:
- Renames the current log file (adds a number or date suffix)
- Creates a new empty log file (optional)
- Signals the application to reopen its log file
- Compresses old logs (optional)
- Deletes logs older than the retention period
The “signal to reopen” step is critical. Most applications open a log file once and keep writing to the same file descriptor. If logrotate just renames the file, the app keeps writing to the renamed file. The app needs to be told to close its current file handle and open the new one.
Default Configuration
The main config is at /etc/logrotate.conf. Individual service configs live in /etc/logrotate.d/. Check what’s already configured:
ls /etc/logrotate.d/
You’ll see entries for nginx, apache2, rsyslog, and others. These are typically installed by the package manager.
Configuration Directives
A typical logrotate config:
/var/log/myapp/*.log {
daily
missingok
rotate 14
compress
delaycompress
notifempty
create 0644 www-data www-data
postrotate
systemctl reload myapp > /dev/null 2>&1 || true
endscript
}
Let me explain each directive:
daily — rotate daily. Other options: weekly, monthly, size 100M (rotate when file exceeds size).
missingok — don’t error if the log file doesn’t exist.
rotate 14 — keep 14 old rotated log files. Combined with daily, that’s 14 days of history.
compress — compress rotated files with gzip.
delaycompress — don’t compress the most recently rotated file. Why? Some applications might still be writing to it. Delay compression by one rotation cycle.
notifempty — don’t rotate if the log file is empty.
create 0644 www-data www-data — create a new empty log file after rotation, with these permissions and owner.
postrotate / endscript — commands to run after rotation. This is where you tell the application to reopen its log files.
Nginx Log Rotation
A good Nginx rotation config:
/var/log/nginx/*.log {
daily
missingok
rotate 30
compress
delaycompress
notifempty
create 0640 www-data adm
sharedscripts
prerotate
if [ -d /etc/logrotate.d/httpd-prerotate ]; then \
run-parts /etc/logrotate.d/httpd-prerotate; \
fi \
endscript
postrotate
invoke-rc.d nginx rotate >/dev/null 2>&1
endscript
}
sharedscripts — run the postrotate script once, even when rotating multiple files. Without this, postrotate runs for each matching file.
invoke-rc.d nginx rotate sends USR1 signal to Nginx, telling it to reopen log files.
Application Log Rotation
For a custom application:
/var/log/myapp/app.log {
daily
missingok
rotate 7
compress
delaycompress
notifempty
create 0644 deploy deploy
postrotate
/usr/bin/kill -USR1 $(cat /var/run/myapp.pid) 2>/dev/null || true
endscript
}
If your application doesn’t support USR1 for log reopening, you might need to restart it instead:
postrotate
systemctl restart myapp
endscript
Restart is more disruptive but works when the app doesn’t implement signal-based log reopening. Some modern apps (especially those that log to stdout, captured by systemd) don’t need a postrotate script at all.
Size-Based Rotation
Rotate when a file gets too large, regardless of time:
/var/log/bigapp/*.log {
size 500M
missingok
rotate 5
compress
notifempty
postrotate
systemctl reload bigapp
endscript
}
size 500M — rotate when the file exceeds 500MB. Can use K, M, or G suffixes.
Combine size and time with maxsize:
daily
maxsize 100M
This rotates daily OR when the file exceeds 100MB, whichever comes first.
Date-Based Naming
By default, logrotate appends numbers: app.log.1, app.log.2.gz, etc. For date-based names:
/var/log/myapp/*.log {
daily
rotate 30
compress
dateext
dateformat -%Y-%m-%d
}
This creates files like app.log-2024-01-15.gz. Easier to find specific dates.
Testing Configuration
Test a config without actually rotating:
logrotate --debug /etc/logrotate.d/myapp
Debug mode shows what logrotate would do without making changes. Always test before deploying a new config.
Force rotation immediately (ignoring time/size conditions):
logrotate --force /etc/logrotate.d/myapp
Useful to test that the postrotate script works correctly.
The State File
Logrotate tracks when each log was last rotated in /var/lib/logrotate/status:
cat /var/lib/logrotate/status
Each line shows the log file path and when it was last rotated. If logrotate seems to be not rotating on schedule, check this file.
Common Issues
Logs not rotating — check the state file (maybe it was rotated recently), check that the log file matches the glob pattern in the config, verify the config syntax with logrotate --debug.
Application still writing to old file — the postrotate script might not be working. Check that it’s actually sending the right signal or reloading the service. Test manually.
Ownership issues after rotation — the create directive sets permissions for new files. If your application runs as a specific user, match it in create.
Compression not working — make sure gzip is installed. Check that delaycompress isn’t preventing compression when you expect it.
Verifying Disk Usage
After setting up rotation, check that it’s working over time:
du -sh /var/log/*
ls -lh /var/log/nginx/
Healthy log directories should stay roughly constant in size after rotation is established. If they’re still growing, either rotation isn’t running or the retention is too long.
Set up monitoring for disk usage — I use Netdata or just a simple cron-triggered email when a disk exceeds a threshold. Logrotate prevents the common case of gradual log accumulation, but it won’t save you from an application generating 10GB of logs per day.