TL;DR — Quick Summary
Logrotate keeps Linux log files under control. Learn key directives, postrotate scripts, size-based rotation, and how to test your configuration safely.
Managing log files on Linux is a routine operations task that, when neglected, leads to full disks, degraded performance, and failed services. Logrotate is the standard tool for automating log rotation, compression, and cleanup across virtually every Linux distribution. This guide covers everything from the global config to per-app examples, testing strategies, and troubleshooting real-world issues.
Prerequisites
- Linux server running Ubuntu, Debian, RHEL, or a compatible distribution.
logrotateinstalled (apt install logrotateoryum install logrotate).- Basic familiarity with the command line and service management via
systemctl. - Root or sudo access to edit
/etc/logrotate.confand/etc/logrotate.d/.
How Logrotate Works
Logrotate does not run as a daemon. It is invoked once daily by the cron entry at /etc/cron.daily/logrotate. Each time it runs, it reads its configuration, checks the state file at /var/lib/logrotate/status to determine when each log was last rotated, and acts only when the scheduled interval has elapsed.
# Check the state file manually
cat /var/lib/logrotate/status
The state file records entries like:
"/var/log/nginx/access.log" 2026-3-21-6:0:0
"/var/log/syslog" 2026-3-22-6:0:0
If this file becomes corrupted or is deleted, logrotate will rotate all logs on its next run as if they have never been rotated.
Global Config vs Per-App Configs
/etc/logrotate.conf — Global Defaults
This file sets defaults inherited by all configurations:
# Rotate weekly by default
weekly
# Keep 4 rotations
rotate 4
# Compress rotated logs
compress
# Load per-app configs
include /etc/logrotate.d
Any directive set here applies globally unless overridden in a per-app file.
/etc/logrotate.d/ — Per-Application Overrides
Each file in this directory configures rotation for one application. The filename is arbitrary (conventionally the app name), and a single file can manage multiple log paths:
/var/log/nginx/access.log /var/log/nginx/error.log {
daily
rotate 14
compress
delaycompress
missingok
notifempty
create 0640 www-data adm
postrotate
systemctl reload nginx
endscript
}
Key Directives
| Directive | Effect |
|---|---|
daily / weekly / monthly | Rotation frequency |
rotate N | Number of rotated files to keep before deletion |
compress | Compress rotated files with gzip |
delaycompress | Compress the previous rotation, not the just-rotated one |
missingok | Silently skip if log file is missing |
notifempty | Skip rotation if the log is empty |
create mode owner group | Create a new empty log with these permissions after rotation |
sharedscripts | Run postrotate once for all matched files, not once per file |
dateext | Append a date stamp instead of a numeric suffix to rotated files |
size N / minsize N / maxsize N | Trigger rotation based on file size |
copytruncate vs postrotate
This is the most critical architectural decision for each application you manage.
postrotate / endscript (preferred)
After logrotate renames the log file, the postrotate block runs a command that tells the application to close the old file descriptor and open the new empty file:
postrotate
systemctl reload nginx
# or for processes that use SIGUSR1:
kill -USR1 $(cat /var/run/gunicorn.pid) 2>/dev/null || true
endscript
This is the correct approach for Nginx, Apache, Gunicorn, and any service that handles reload or SIGUSR1 gracefully.
copytruncate
Some applications (custom scripts, certain Java apps) cannot reload file handles. copytruncate instructs logrotate to copy the file to the rotated name, then truncate the original to zero bytes:
/var/log/myapp/app.log {
daily
rotate 7
compress
delaycompress
copytruncate
}
Caution: There is a tiny window between the copy and the truncation where new log lines are written to the original file. These lines end up in the live file, not the copy — meaning they are lost from the rotated archive. For high-volume logs, prefer postrotate if at all possible.
Size-Based Rotation
Time-based rotation is not always sufficient. A suddenly verbose application can fill a disk before the daily cron runs. Use these directives to add size triggers:
/var/log/myapp/app.log {
# Rotate when file exceeds 100 MB regardless of schedule
size 100M
# Only rotate when file is at least 10 MB AND the time interval has elapsed
# minsize 10M
# Rotate when file exceeds 500 MB, even if the interval has not elapsed
# maxsize 500M
rotate 10
compress
delaycompress
missingok
notifempty
}
size overrides the time schedule entirely. minsize requires both the size threshold AND the time interval to be met. maxsize rotates immediately on size regardless of schedule, similar to size.
Custom Examples
Nginx
/var/log/nginx/*.log {
daily
rotate 30
compress
delaycompress
missingok
notifempty
create 0640 www-data adm
sharedscripts
postrotate
nginx -s reopen
endscript
}
nginx -s reopen sends SIGUSR1 to the master process, which causes all workers to reopen their log files.
Apache
/var/log/apache2/*.log {
weekly
rotate 52
compress
delaycompress
missingok
notifempty
create 640 root adm
sharedscripts
postrotate
if invoke-rc.d apache2 status > /dev/null 2>&1; then
invoke-rc.d apache2 reload > /dev/null 2>&1
fi
endscript
prerotate
if [ -d /etc/logrotate.d/httpd-prerotate ]; then
run-parts /etc/logrotate.d/httpd-prerotate
fi
endscript
}
Docker Container Logs
Docker writes JSON log files to /var/lib/docker/containers/<id>/<id>-json.log. You can rotate these with logrotate as a fallback when the Docker daemon’s --log-opt max-size is not configured:
/var/lib/docker/containers/*/*.log {
daily
rotate 7
compress
delaycompress
missingok
notifempty
copytruncate
}
Note: copytruncate is required here because there is no way to signal Docker to reopen its log files on a per-container basis. The better long-term solution is to configure the Docker daemon with "log-driver": "json-file" and "log-opts": {"max-size": "100m", "max-file": "5"} in /etc/docker/daemon.json.
PostgreSQL
/var/log/postgresql/*.log {
weekly
rotate 10
compress
delaycompress
missingok
notifempty
create 0640 postgres postgres
sharedscripts
postrotate
pg_ctlcluster $(pg_lsclusters -h | awk '{print $1, $2}' | head -1) logrotate 2>/dev/null || true
endscript
}
Application Logs in /var/log/myapp/
/var/log/myapp/*.log {
daily
rotate 14
size 50M
compress
delaycompress
missingok
notifempty
create 0640 myapp myapp
sharedscripts
postrotate
systemctl reload myapp 2>/dev/null || true
endscript
}
Date-Based Filenames with dateext
By default, logrotate appends numeric suffixes: access.log.1, access.log.2.gz. Enable dateext for human-readable date stamps:
/var/log/nginx/*.log {
daily
rotate 30
dateext
dateformat -%Y-%m-%d
compress
delaycompress
missingok
}
This produces filenames like access.log-2026-03-22.gz — much easier to audit.
Testing Your Configuration
Dry Run (Safe)
# Simulate without making any changes
sudo logrotate -d /etc/logrotate.d/nginx
Output shows which files would be rotated, which scripts would run, and any errors in the config.
Force Rotation
# Force rotation right now regardless of schedule
sudo logrotate -f /etc/logrotate.d/nginx
Verbose Mode
sudo logrotate -v /etc/logrotate.conf
Check the result:
ls -lh /var/log/nginx/
cat /var/lib/logrotate/status | grep nginx
Troubleshooting
| Problem | Cause | Solution |
|---|---|---|
| Rotation runs but logs keep growing | notifempty skipping empty logs, or app not respecting reload | Check if the log has content; verify the app reopens files on systemctl reload |
| ”error: skipping ‘/var/log/myapp’ because parent directory has insecure permissions” | logrotate refuses to rotate if a parent dir is world-writable | Fix directory permissions: chmod o-w /var/log/myapp |
| Rotated files not compressed | Missing compress, or using compress without delaycompress for apps holding the file | Add delaycompress to compress on the next cycle |
| SELinux blocking rotation | SELinux context mismatch on rotated files | Run restorecon -v /var/log/myapp/*.log after rotation, or add a postrotate rule |
| State file corruption | Manual edits or filesystem errors | Delete /var/lib/logrotate/status — logrotate recreates it and treats all logs as never rotated |
postrotate script silently fails | Exit code of the script is non-zero | Add ` |
Comparison: Logrotate vs Other Approaches
| Tool | Mechanism | Best For |
|---|---|---|
| logrotate | Cron-triggered file rotation with configurable rules | Traditional syslog/app log files in /var/log/ |
| systemd-journald | Binary journal with SystemMaxUse and MaxFileSec size/age limits | systemd service journal entries (journalctl) |
| rsyslog built-in rotation | om file module with rotation directives | Sites already using rsyslog for centralized log forwarding |
| Docker logging drivers | json-file driver with max-size and max-file options | Container stdout/stderr logs managed by the Docker daemon |
| Fluentd / Vector | Ship logs to a remote store; no local rotation needed | High-volume, cloud-native environments with centralized log aggregation |
For a typical Linux server running Nginx, PostgreSQL, and custom applications, logrotate is the right choice. Use journald for systemd service output alongside logrotate for application files — they are complementary, not competing.
Summary
- Logrotate runs daily via
/etc/cron.daily/logrotateand uses/var/lib/logrotate/statusto track rotation times. - Global defaults live in
/etc/logrotate.conf; per-app configs go in/etc/logrotate.d/. - Use
postrotatewithsystemctl reloadorkill -USR1whenever possible — prefer it overcopytruncate. - Add
delaycompressalongsidecompressto avoid compressing files still held open by running processes. - Use
size,minsize, ormaxsizedirectives for services that can spike log volume unexpectedly. - Always test with
logrotate -dbefore deploying a new config to production. - Date-based filenames (
dateext) make log archives much easier to manage and audit.