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, theninclude /etc/logrotate.d). - Per-package configs:
/etc/logrotate.d/*. - Scheduler:
logrotate.timer(systemd) on current Debian/Ubuntu/RHEL, or/etc/cron.daily/logrotateon 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:
sharedscriptsrunspostrotateonce for the whole glob instead of once per file.- The service is
named.serviceon current Debian (with abind9alias) and RHEL.rndc reconfigis an alternative that makes BIND reopen its files. - BIND can also rotate its own channel files (
versionsandsizein thelogging { 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): renameapp.log→app.log.1, create a freshapp.log, then signal the app (postrotate) to reopen. No data loss. Requires the app to support reopening (SIGHUP,reload,USR1, etc.).copytruncate: copyapp.logtoapp.log.1, then truncateapp.logto 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)'