Andrew Mercer
on this page

logrotate renames, compresses and deletes old plain-text log files on a schedule so they don't grow forever. It does not manage the journal (journald does that itself).

How it runs

  • Main config: /etc/logrotate.conf (global defaults, then include /etc/logrotate.d).
  • Per-package configs: /etc/logrotate.d/*.
  • Scheduler: logrotate.timer (systemd) on current Debian/Ubuntu/RHEL, or /etc/cron.daily/logrotate on older systems.
  • State file records when each log was last rotated: /var/lib/logrotate/status (Debian/Ubuntu) or /var/lib/logrotate/logrotate.status (RHEL).
systemctl list-timers logrotate.timer
systemctl status logrotate.service
journalctl -u logrotate.service -n 50

Creating a new config: BIND example

There's no need to copy another package's file as a starting point; just create one. A glob avoids listing every channel file.

/etc/logrotate.d/named-custom:

/var/log/named/*.log {
    daily
    rotate 14
    missingok
    notifempty
    compress
    delaycompress
    dateext
    create 0640 bind bind          # use named:named on RHEL
    su bind bind                   # needed if /var/log/named isn't root-owned
    sharedscripts
    postrotate
        /usr/bin/systemctl reload named >/dev/null 2>&1 || true
    endscript
}

Notes:

  • sharedscripts runs postrotate once for the whole glob instead of once per file.
  • The service is named.service on current Debian (with a bind9 alias) and RHEL. rndc reconfig is an alternative that makes BIND reopen its files.
  • BIND can also rotate its own channel files (versions and size in the logging { channel … } block). Use one mechanism or the other, never both.

Testing and forcing

# Dry run: show exactly what would happen, change nothing
sudo logrotate -d /etc/logrotate.d/named-custom

# Dry run of the whole configuration (includes global defaults)
sudo logrotate -d /etc/logrotate.conf 2>&1 | grep -A5 /var/log/named

# Force a rotation now, verbosely
sudo logrotate -vf /etc/logrotate.d/named-custom

# Test with a throwaway state file so you don't disturb the real schedule
sudo logrotate -vf -s /tmp/logrotate.test.state /etc/logrotate.d/named-custom

When you pass a single file from /etc/logrotate.d/, the global defaults in /etc/logrotate.conf (such as compress or create) are not applied. That can make a forced single-file test behave differently from the nightly run. Put every directive you depend on in the per-app file, or test against /etc/logrotate.conf.

Directive reference

Directive Effect
daily / weekly / monthly / yearly Rotation frequency
hourly Hourly, only if logrotate itself is run hourly
rotate N Keep N rotated files (0 = delete immediately)
size 100M Rotate when larger than this, regardless of time
minsize 10M Rotate on schedule, but only if at least this large
maxsize 500M Rotate on schedule or when larger than this
maxage 90 Delete rotated files older than N days
missingok Don't error if the log is missing
notifempty Skip empty files
compress / nocompress gzip rotated files (see compresscmd, compressext for zstd/xz)
delaycompress Leave the most recent rotated file (.1) uncompressed until the next cycle
create MODE OWNER GROUP Create a new empty log after rotating
copytruncate Copy the log, then truncate in place (for apps that can't reopen files)
dateext / dateformat -%Y%m%d Date-stamped names instead of .1, .2 …
olddir DIR Move rotated files to another directory (same filesystem unless copy)
su USER GROUP Rotate as this user/group (needed for non-root-owned directories)
sharedscripts Run pre/postrotate once per stanza, not per file
prerotate / postrotate … endscript Shell commands before/after rotation
firstaction / lastaction Run once before/after all files in the stanza
tabooext File extensions ignored in /etc/logrotate.d

create vs copytruncate

  • create (default): rename app.log → app.log.1, create a fresh app.log, then signal the app (postrotate) to reopen. No data loss. Requires the app to support reopening (SIGHUP, reload, USR1, etc.).
  • copytruncate: copy app.log to app.log.1, then truncate app.log to zero. Works with apps that never reopen files, but anything written between the copy and the truncate is lost. The app must also open the file in append mode, otherwise you get a sparse file full of null bytes.

Prefer create + a reload hook whenever the application supports it.

Rotating rsyslog outputs

Any new file you create with rsyslog (cron.log, firewall.log, the remote per-host tree) must be added to logrotate. Either extend the distro stanza or add your own:

/etc/logrotate.d/rsyslog-custom:

/var/log/cron.log
/var/log/firewall.log
{
    weekly
    rotate 8
    missingok
    notifempty
    compress
    delaycompress
    sharedscripts
    postrotate
        /usr/bin/systemctl kill -s HUP rsyslog.service >/dev/null 2>&1 || true
    endscript
}

/var/log/remote/*.log
/var/log/remote/*/*.log
{
    daily
    rotate 30
    missingok
    notifempty
    compress
    delaycompress
    dateext
    sharedscripts
    postrotate
        /usr/bin/systemctl kill -s HUP rsyslog.service >/dev/null 2>&1 || true
    endscript
}

Debian/Ubuntu ship /usr/lib/rsyslog/rsyslog-rotate, which does the same HUP and can be used in postrotate instead.

Use globs that match only live logs (*.log), never a bare *. A pattern like /var/log/remote/* also matches already-rotated .1 and .gz files and leads to .1.1.gz.gz chains.

Confirm the files are picked up:

sudo logrotate -d /etc/logrotate.conf 2>&1 | grep -E '/var/log/(remote|cron|firewall)'