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.
  • logrotate installed (apt install logrotate or yum install logrotate).
  • Basic familiarity with the command line and service management via systemctl.
  • Root or sudo access to edit /etc/logrotate.conf and /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

DirectiveEffect
daily / weekly / monthlyRotation frequency
rotate NNumber of rotated files to keep before deletion
compressCompress rotated files with gzip
delaycompressCompress the previous rotation, not the just-rotated one
missingokSilently skip if log file is missing
notifemptySkip rotation if the log is empty
create mode owner groupCreate a new empty log with these permissions after rotation
sharedscriptsRun postrotate once for all matched files, not once per file
dateextAppend a date stamp instead of a numeric suffix to rotated files
size N / minsize N / maxsize NTrigger 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

ProblemCauseSolution
Rotation runs but logs keep growingnotifempty skipping empty logs, or app not respecting reloadCheck 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-writableFix directory permissions: chmod o-w /var/log/myapp
Rotated files not compressedMissing compress, or using compress without delaycompress for apps holding the fileAdd delaycompress to compress on the next cycle
SELinux blocking rotationSELinux context mismatch on rotated filesRun restorecon -v /var/log/myapp/*.log after rotation, or add a postrotate rule
State file corruptionManual edits or filesystem errorsDelete /var/lib/logrotate/status — logrotate recreates it and treats all logs as never rotated
postrotate script silently failsExit code of the script is non-zeroAdd `

Comparison: Logrotate vs Other Approaches

ToolMechanismBest For
logrotateCron-triggered file rotation with configurable rulesTraditional syslog/app log files in /var/log/
systemd-journaldBinary journal with SystemMaxUse and MaxFileSec size/age limitssystemd service journal entries (journalctl)
rsyslog built-in rotationom file module with rotation directivesSites already using rsyslog for centralized log forwarding
Docker logging driversjson-file driver with max-size and max-file optionsContainer stdout/stderr logs managed by the Docker daemon
Fluentd / VectorShip logs to a remote store; no local rotation neededHigh-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/logrotate and uses /var/lib/logrotate/status to track rotation times.
  • Global defaults live in /etc/logrotate.conf; per-app configs go in /etc/logrotate.d/.
  • Use postrotate with systemctl reload or kill -USR1 whenever possible — prefer it over copytruncate.
  • Add delaycompress alongside compress to avoid compressing files still held open by running processes.
  • Use size, minsize, or maxsize directives for services that can spike log volume unexpectedly.
  • Always test with logrotate -d before deploying a new config to production.
  • Date-based filenames (dateext) make log archives much easier to manage and audit.