Guides
Guide

logrotate: How to Configure Log Rotation on Linux

By Sazabi Team

Last verified on Nov 8, 2026.

logrotate keeps log files from filling a disk. On a schedule, or when a file passes a size, it renames the live file, starts a new one, compresses old copies, and deletes copies beyond the count you set. You configure it with a block per log file in /etc/logrotate.conf or /etc/logrotate.d/, and you check the result with logrotate -d.

How logrotate Runs

logrotate is a program you run, not a daemon. The man page says it is normally run as a daily cron job, or by logrotate.timer under systemd. It will not touch a log more than once a day unless the rule is based on size and logrotate runs more than once a day, or unless you pass -f. That has one consequence worth remembering: hourly in a config does nothing useful unless something actually runs logrotate every hour.

It records what it did in a state file, /var/lib/logrotate.status by default, and uses it to decide when a log was last rotated. The stock /etc/logrotate.conf on Debian and Ubuntu sets global defaults (weekly, rotate 4, create, and su root adm) and ends with include /etc/logrotate.d. Packages drop one file per service into that directory.

A Working Configuration

This block is for an application that writes /var/log/myapp/app.log:

/var/log/myapp/*.log {
    daily
    rotate 14
    maxsize 100M
    missingok
    notifempty
    compress
    delaycompress
    dateext
    dateformat -%Y%m%d-%s
    create 0640 myapp adm
    sharedscripts
    postrotate
        kill -USR1 "$(cat /run/myapp.pid)"
    endscript
}

What each line does, from the man page:

DirectiveEffect
dailyRotate once a day. weekly, monthly, and yearly also exist.
rotate 14Keep 14 old versions, then remove the oldest. The default is 0, which removes old versions instead of keeping any.
maxsize 100MRotate when the file grows past 100M even before the time interval. Both size and age are considered.
missingokIf the file is absent, move on without an error. The default is to report an error.
notifemptyDo not rotate an empty file.
compressCompress old versions, with gzip by default.
delaycompressLeave the most recent old copy uncompressed until the next cycle.
dateextName old files with a date suffix instead of a number.
create 0640 myapp admCreate a new log file immediately after rotation, before postrotate runs, with this mode, owner, and group.
sharedscriptsRun postrotate once for the whole pattern instead of once per file.

Pick size, maxsize, or minsize deliberately. size 100M rotates only on size and ignores the schedule, maxsize adds a size trigger to a schedule, and minsize rotates when the file is large enough and the interval has also passed. Of the options that are mutually exclusive with the time intervals, the last one written wins.

Tell the Program to Re-Open Its Log

After rotation the program still holds the old file open and keeps writing to it. That is the main way rotation goes wrong. You have two options.

Signal the program. The postrotate script runs after the file is rotated. Many daemons re-open their logs on a signal. nginx re-opens log files on USR1, which is why its packaged config calls a rotate action, and the man page examples use HUP for syslog and httpd. See the nginx logs guide for the nginx case. Check what your own program expects before you copy a signal.

Use copytruncate. logrotate copies the file, then truncates the original in place, so the program keeps writing to the same file with no signal:

/var/log/legacy/app.log {
    rotate 7
    size 50M
    copytruncate
    compress
}

The man page is direct about the cost: there is a very small time slice between the copy and the truncate, so some logging data might be lost. With copytruncate, create has no effect because the old file stays in place. Prefer a signal or a logger that can re-open its file, and use copytruncate for programs that cannot.

delaycompress helps when a program cannot be told to close its log and might keep writing to the previous file for a while. Compression then waits one more cycle.

Test Before You Trust It

sudo logrotate -d /etc/logrotate.d/myapp          # debug: no changes, no state update
sudo logrotate -v -f /etc/logrotate.d/myapp       # really rotate now, verbose
sudo cat /var/lib/logrotate.status                # last rotation time per file

-d prints what it would do and changes neither the files nor the state file. -f forces a rotation even when logrotate thinks none is needed, which is useful after adding a new block. Running a similar block against logrotate 3.22.0 with a test log, -d printed log needs rotating, the rename target, and creating new ... mode = 0640, and the forced run produced a fresh empty app.log and a dated copy.

If logrotate runs as a non-root user, use -s for a separate state file. By default it locks the state file, and if the lock cannot be taken it exits with value 3.

Avoid the dateext Collision

The default dateext suffix is -YYYYMMDD. When a maxsize rule fires twice on the same day, the second rotation wants the same name. On logrotate 3.22.0 a forced second rotation on the same day printed:

error: destination /tmp/lr/logs/app.log-20261108 already exists, skipping rotation

The rotation is skipped and the file keeps growing. Give each rotation a unique suffix with dateformat -%Y%m%d-%s, as in the example above. The man page lists %s among the allowed specifiers. Two forced rotations one second apart then produced two distinct files.

Containers and the Journal

Container logs and the systemd journal do not use logrotate.

Docker's default json-file driver rotates through its own options. Set max-size and max-file in /etc/docker/daemon.json:

{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  }
}

Docker documents max-size as unlimited by default and max-file as only effective when max-size is set. The values must be strings, and the change applies to containers created after you restart Docker, not existing ones.

The journal has its own limits. Use SystemMaxUse= and journalctl --vacuum-size=, covered in the journalctl guide. To read container logs on Kubernetes, see the kubectl logs guide.

Rotation Is Not Retention

rotate 14 and daily mean 14 days of files on one host, and they disappear when the host does. If you need logs after an instance is replaced, or for an investigation after the disk was wiped, ship them off the host before rotation removes them. Decide how long to keep each kind of log by what you will need to answer, then set both the local rotation and the central log retention to match. Audit and security logs usually need longer than debug output.

Shipping Rotated Logs to Sazabi

Rotation and shipping are separate jobs. A forwarder such as the OpenTelemetry Collector or Fluent Bit tails the live file and sends lines as they appear, so rotation only controls local disk. Sazabi, an AI-native observability and security platform, stores what you send in managed storage. Its storage docs say retention depends on your plan and records past the window are removed automatically, so local rotate counts do not set how long Sazabi keeps data. See the OpenTelemetry Collector and Fluent Bit integration pages for the forwarder side. The Collector's filelog receiver reads files rotated by either strategy, move/create or copy/truncate. It tracks files by inode and content fingerprint, and has an on_truncate option (default ignore) for the copytruncate case, so check that setting if you use copytruncate. Remember the man page warning that lines can be lost in the copy window, and compare line counts at the destination after you change rotation settings.

Frequently Asked Questions

What does logrotate do?

logrotate renames or copies a log file on a schedule or at a size, optionally compresses the old copy, keeps a fixed number of old copies, and then deletes the rest. It is normally run once a day by cron or a systemd timer.

How do I test a logrotate configuration without changing files?

Run logrotate -d /path/to/config. Debug mode prints what it would do and changes neither the logs nor the state file. Use -f to force a real rotation, and -v to see what happens.

When should I use copytruncate?

Use it only when the program cannot be told to re-open its log file. logrotate copies the file and truncates the original in place, and the man page warns that some lines written between the copy and the truncate can be lost.

Why does logrotate say the destination already exists?

With dateext, the default suffix is only the date, so a second rotation on the same day tries to create a file that is already there. Set dateformat with seconds, for example dateformat -%Y%m%d-%s, or avoid size rules that rotate several times a day.

Sources

  1. logrotate directives: rotate, daily, size, maxsize, minsize, compress, delaycompress, copytruncate, create, dateext, dateformat, sharedscripts, postrotate, missingok, notifempty, and the -d and -f flags (accessed Nov 8, 2026)
  2. The Docker json-file driver rotates with max-size and max-file, and max-file only takes effect when max-size is set (accessed Nov 8, 2026)
  3. Sending USR1 to the nginx master process re-opens log files (accessed Nov 8, 2026)
  4. The systemd journal has its own size limits (SystemMaxUse=), separate from file rotation (accessed Nov 8, 2026)
  5. The filelog receiver handles move/create and copy/truncate rotation, tracks files by inode and fingerprint, and has an on_truncate option (accessed Nov 8, 2026)
  6. Sazabi retention depends on the plan, and records older than the retention window are removed automatically (accessed Nov 8, 2026)