Configure logrotate and manage logs on a VPS

A VPS with no log rotation is a ticking clock. A busy Nginx server can write 500 MB of access logs in a week. A PHP error log on a WordPress site under attack might fill 2 GB in a single day. You log in and get No space left on device, sites go down, SSH drops out, apt refuses to run. I have cleaned that mess too many times. The fix is logrotate, and it ships with almost every Linux distribution. This guide walks through configuring it on a Linux VPS running Ubuntu 24.04 LTS or AlmaLinux 9, with commands you can copy and verify.
- Key takeaways, logrotate is installed by default but runs only for a few system services. You must add configurations for your own apps. Testing with
logrotate --debugcosts nothing. Compression saves 70-90% of disk. Daily rotation with 7-day retention is a safe starting point for most workloads.
Prerequisites
- A VPS running Ubuntu 24.04 LTS (or Debian 12) or AlmaLinux 9 / Rocky Linux 9.
- Root or a sudo user.
- Basic familiarity with navigating the filesystem and reading log files.
Why log management matters, and where it breaks
Log files are append-only. They grow until you stop them. I once saw a staging server that had been running for six months with no logrotate. The /var/log directory was 23 GB. The syslog file alone was 8 GB. The server did not crash, but df -h showed 98% usage and any restore operation failed because there was no room for temporary files.
Logrotate solves this by three actions: rotate (rename the current log and start a new one), compress (gzip old logs), and delete (remove logs older than a retention period). It runs from a systemd timer or cron job once per day. On a standard VPS, even a 2 GB RAM VPS, logrotate completes in under two seconds because most directories are small.
Step 1, Locate the existing logrotate setup
Before writing anything, confirm logrotate is installed and understand how it is wired.
which logrotate
logrotate --version
Expected: a path like /usr/sbin/logrotate and a version. On Ubuntu 24.04 you will see 3.22.0 (the 2024 release). On AlmaLinux 9 it will be slightly older but still fully functional.
The main config file is /etc/logrotate.conf. All custom rules should go as separate files inside /etc/logrotate.d/. That directory is included by the main config via the line:
include /etc/logrotate.d
Do not edit /etc/logrotate.conf except for global settings (rotation frequency, compression default, date extension). Put your per-service rules into /etc/logrotate.d/.
Step 2, Write a logrotate rule for Nginx (or any web server)
Here is a practical rule for Nginx access and error logs. Create a file /etc/logrotate.d/nginx:
/var/log/nginx/*.log {
daily
rotate 7
compress
delaycompress
missingok
notifempty
dateext
dateformat -%Y-%m-%d
sharedscripts
postrotate
if [ -f /var/run/nginx.pid ]; then
kill -USR1 `cat /var/run/nginx.pid`
fi
endscript
}
What each directive does:
daily, rotate logs every day. Alternatives:weekly,monthly, orsize 100M(rotate when the file reaches 100 MB).rotate 7, keep seven rotated files. After the eighth rotation, the oldest is deleted.compress, gzip old logs.delaycompressleaves the most recent rotated file uncompressed for one cycle (handy for tools likefail2banthat might still read it).missingok, do not error if the log is missing (first run after a fresh install).dateext, append a date stamp instead of a plain number. Withdateformatyou control the format. The output becomesaccess.log-2026-03-15.gzinstead ofaccess.log.1.gz.postrotate, send theUSR1signal to Nginx after rotation so it reopens its log file without dropping connections. The same pattern works for Apache (httpdorapache2).
Step 3, Write a rule for system logs and application logs
System logs like /var/log/syslog or /var/log/messages are usually already handled by /etc/logrotate.d/rsyslog or /etc/logrotate.d/syslog (AlmaLinux uses sysklogd). Verify that these files exist. If they do, they are active. Do not duplicate them.
For custom applications, write a dedicated rule. Example for a Python Flask app logging to /var/log/myapp/app.log:
/var/log/myapp/*.log {
daily
rotate 14
compress
dateext
missingok
notifempty
copytruncate
}
copytruncate is the safe option when the application does not support reopening log files on a signal. It copies the log file to the archive and truncates the original in place. The downside: a few lines of log are lost between the copy and the truncation. For most applications that is acceptable.
Step 4, Test the configuration before it runs live
Never trust a logrotate rule on the first try. Test it with the --debug (dry-run) flag:
logrotate --debug /etc/logrotate.d/nginx 2>&1 | head -40
You will see the decisions logrotate makes: "rotating pattern: /var/log/nginx/*.log … log does not need rotating" if the log was already rotated today, or it will show the renaming and compression actions. Look for the line error: … if something is wrong.
To force a rotation immediately and see real output (use on a test log or during maintenance):
logrotate -f /etc/logrotate.d/nginx
Then check the directory:
ls -lh /var/log/nginx/
You should see at least one gzipped archive (e.g. access.log-2026-03-15.gz).
Step 5, Verify the timer or cron that triggers logrotate
Logrotate does not run itself. It must be triggered. On Ubuntu 24.04 and Debian 12 it runs via systemd:
systemctl list-timers --all | grep logrotate
systemctl status logrotate.timer
You should see logrotate.timer, active and scheduled. The default is daily at the system's calendar event (typically around 6:25 AM). On AlmaLinux 9, logrotate is triggered by a cron job at /etc/cron.daily/logrotate. Both work identically. If neither exists (rare but possible on minimal images), add a cron:
echo "0 3 * * * root /usr/sbin/logrotate /etc/logrotate.conf" > /etc/cron.d/logrotate
This runs logrotate daily at 3:00 AM.
Troubleshooting common failures
Logs stop rotating but the disk is full. Run logrotate -f /etc/logrotate.conf and watch for errors. The most common cause: the log file is owned by a different user than expected. Logrotate runs as root by default, but if an application writes to a log as www-data and the log file is set with 600 permissions, logrotate may fail. Add su www-data www-data at the start of the rule block (inside the braces).
Rotation skipped because log is empty. notifempty is the default. If you want to rotate even empty logs, add ifempty.
Compression errors. Verify gzip is installed. On minimal Docker images you might need apt install gzip or dnf install gzip.
Logs are still growing despite rotation. The application may be caching file handles. For Nginx, the postrotate signal (kill -USR1) is essential, without it, Nginx writes to the old renamed file. For other apps, use copytruncate or configure the app to reopen logs on SIGHUP.
FAQ
How do I check if logrotate ran successfully today?
Check the last modified time of the logrotate state file: stat /var/lib/logrotate/logrotate.status (Ubuntu) or /var/lib/logrotate.status (AlmaLinux). That file records the last rotation date for every configured log. You can also look in /var/log/ for a .gz file dated today.
What retention should I use on a VPS with limited storage?
On a 2 GB RAM VPS with 40 GB NVMe, set rotate 7 for daily logs. That gives you a week of history. For error logs, rotate 14 is reasonable because they are smaller. If you run high-traffic sites, consider using size 200M instead of daily so logs rotate when they reach 200 MB regardless of the day.
Should I compress all rotated logs?
Yes. Gzip compression reduces 500 MB to about 30 MB. The CPU cost is negligible, logrotate compresses one log per day, and on modern vCPUs it takes under a second. Add delaycompress if you use tools like fail2ban or logwatch that scan the most recent archive.
What about Docker container logs?
Docker writes logs to JSON files under /var/lib/docker/containers/*/*.log. These can grow without limit. Set a log driver limit in /etc/docker/daemon.json: "log-driver": "json-file", "log-opts": {"max-size": "10m","max-file": "3"}. Alternatively, create a logrotate rule for Docker logs with copytruncate. The Docker path is not under /var/log, so the system configs will not cover it.
How do I rotate logs hourly?
Modify the systemd timer or cron to run hourly: systemctl edit logrotate.timer and set OnCalendar=hourly. Then change the rule from daily to size 10M or keep it daily with rotate 168 (7 days × 24 hours). Hourly rotation is rare, use it only if a single log file grows tens of gigabytes per day.
Related articles
- Set up LEMP stack (Nginx, MariaDB, PHP 8.3) on Ubuntu 24.04
- Nginx tuning for high traffic: worker, gzip, buffer, cache
- Configure swap and optimize memory on a low-RAM VPS
- Security audit and hardening with Lynis on a VPS


