Logrotate is the standard Linux tool for managing log files. It automatically archives, compresses, and deletes old logs on a schedule.
How logrotate works
- Runs via cron (
/etc/cron.daily/logrotate) - Reads configs from
/etc/logrotate.confand/etc/logrotate.d/ - Applies rules to each log: rotate, compress, delete
Config for Nginx
sudo nano /etc/logrotate.d/nginx
/var/log/nginx/*.log {
daily
missingok
rotate 14
compress
delaycompress
notifempty
create 0640 www-data adm
sharedscripts
postrotate
nginx -s reopen
endscript
}
Config for your application
sudo nano /etc/logrotate.d/myapp
/var/www/myapp/logs/*.log {
weekly
rotate 8
compress
delaycompress
missingok
notifempty
create 0644 www-data www-data
}
Testing and running
# Test without actually running it
sudo logrotate -d /etc/logrotate.d/nginx
# Force run
sudo logrotate -f /etc/logrotate.conf
# Check status
sudo cat /var/lib/logrotate/status
| Directive | Value |
|---|---|
| daily/weekly/monthly | Rotation frequency |
| rotate N | Keep N archives |
| compress | Compress with gzip |
| missingok | Don't error if the file is missing |
df -h. If /var is full — run logrotate -f /etc/logrotate.conf to force it.Diagnosing common errors
The error error: skipping "/var/log/nginx/access.log" because parent directory has insecure permissions appears when the log directory is writable by group or by everyone — logrotate refuses to work with such paths for security reasons, so nobody can swap the log for a symlink to a system file.
error: skipping "/var/log/nginx/access.log" because parent directory has insecure permissions
(It's world writable or writable by group which is not "root")
The fix is to set the directory permissions with chmod 755 /var/log/nginx, not to widen the list of trusted groups in /etc/logrotate.conf — that would be a security hole, not a workaround. The su directive in a rule (su www-data www-data) is sometimes used instead of fixing permissions, but it only changes which user logrotate rotates the file as, and does not bypass the directory security check itself.
After configuring the rule the log still grows forever even though the rule looks correct. A common cause is that the process keeps the old file descriptor open even after rotation: without a postrotate step that signals the file to reopen (nginx -s reopen, systemctl reload myapp), the application keeps writing to the already-renamed and abandoned access.log.1 file, while the new access.log stays empty.
directory vs default: where to look for the problem
| Symptom | Likely cause | Check |
|---|---|---|
| The rule never fires | A syntax error in the config | logrotate -d shows the parse error |
| File rotates but is not compressed | The compress directive is missing or commented out | Check the resulting config line by line |
| New file empty, old one keeps growing | The application did not reopen its descriptor | Check whether postrotate exists and what it does |
| "insecure permissions" | Log directory permissions are too open | ls -ld /var/log/directory-name |
When logrotate does not fit
- The application already logs through
journald— rotation and retention are handled byjournalctl --vacuum-size/--vacuum-time, not logrotate; running both mechanisms is redundant. - You need centralized log aggregation from multiple servers with full-text search — logrotate only manages files locally, so aggregation needs a stack like Grafana Loki.
- Logs must rotate by size in real time rather than once a day by cron — logrotate runs on the cron schedule instead of watching the file continuously; instant size-based rotation needs a tool like
logrotaterun more often, or an application with its own rotation. - A containerized application logs to stdout rather than a file — rotation of stdout logs is handled by the container engine itself through its logging driver (
json-filewith a size cap), not by logrotate inside the container.
Verifying the result
- Check the syntax without actually rotating:
logrotate -d /etc/logrotate.d/nginx— the output must not contain anyerrorlines. - Force a rotation:
logrotate -f /etc/logrotate.d/nginxand confirm a file likeaccess.log.1.gzappears. - Confirm the application keeps writing to the new file rather than the old one:
tail -f /var/log/nginx/access.logmust show new requests right after rotation. - Check the status of the last run:
cat /var/lib/logrotate/status | grep nginxmust show today's date.
Freeing disk space from already-accumulated old logs is covered separately in the article on logrotate on a VPS. For systemd service logs with no files on disk, see the article on journalctl and rsyslog.
Check Existing Rules
# View all logrotate configs
ls /etc/logrotate.d/
# Test without an actual rotation
sudo logrotate --debug /etc/logrotate.conf
Create a Rule for Your Application
# /etc/logrotate.d/myapp
/var/log/myapp/*.log {
daily # Rotate every day
missingok # Don't error if the file is missing
rotate 14 # Keep 14 archives
compress # Compress with gzip
delaycompress # Don't compress the latest archive
notifempty # Don't rotate empty files
create 0640 www-data www-data # Permissions for the new file
postrotate
systemctl reload nginx > /dev/null 2>&1 || true
endscript
}
Logrotate Parameters
| Parameter | Description |
|---|---|
| daily/weekly/monthly | Rotation period |
| rotate N | Number of archives |
| size 100M | Rotate by size |
| compress | Compress with gzip |
| maxage 30 | Delete archives older than 30 days |
| sharedscripts | postrotate runs once |
Forcing Rotation Right Now
sudo logrotate --force /etc/logrotate.d/myapp
Freeing Up Space From Old Logs
# Find large log files
sudo find /var/log -name "*.log" -size +100M
# Clear a file without deleting it (the process keeps writing)
sudo truncate -s 0 /var/log/nginx/access.log
# Or via journald
sudo journalctl --vacuum-size=500M
du vs df: finding where the space went
| Symptom | Diagnostic command | What it shows |
|---|---|---|
| df sees less free space than du | lsof +L1 | Deleted but still open files |
| Unclear which directory is heaviest | du -h --max-depth=1 /var/log | sort -rh | The size of each log subdirectory |
| Logs grow faster than expected | watch -n 5 'du -sh /var/log/nginx' | The growth rate in real time |
When regular rotation is not enough
- The disk is already full right now, not just at risk of filling up later —
logrotate --forcewill not help instantly if open deleted files are the cause; the processes holding them must be restarted first. - Logs accumulate faster than the disk can hold even with compression — logs need to move to a separate volume, object storage, or a centralized system like Grafana Loki, rather than more aggressive rotation on the same disk.
- You need to find the specific cause of abnormal log growth, not just free up space — that is a log-analysis task for errors and suspicious traffic, not a rotation task.