Optimization

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 --debug costs 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, or size 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. delaycompress leaves the most recent rotated file uncompressed for one cycle (handy for tools like fail2ban that 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. With dateformat you control the format. The output becomes access.log-2026-03-15.gz instead of access.log.1.gz.
  • postrotate, send the USR1 signal to Nginx after rotation so it reopens its log file without dropping connections. The same pattern works for Apache (httpd or apache2).

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

Note: This guide is for general reference. Every system and infrastructure has its own specifics, so test each step in a safe environment and consult a qualified engineer before applying it in production.