Build a Safe Logrotate Policy and Test It Without Surprises
You will finish with a small logrotate policy that rotates a test log, keeps a bounded number of archives, compresses older archives, and can be inspected before it changes anything. The examples target logrotate 3.21.0, installed here as package version 3.21.0-2build1.
The route
Jump straight to the step you need, or tick off Done means at the end.
Allow about twenty minutes. You need a shell and permission to create files under a temporary directory. The first half is unprivileged and changes only temporary files. Applying a policy to a real system log normally needs elevated privileges and can affect a running service, so the guide keeps that step separate.
1. Check the installed command
Start by checking the binary and its defaults. This is read-only and does not need sudo:
$ command -v logrotate
/usr/sbin/logrotate
$ logrotate --version
logrotate 3.21.0
Default mail command: /usr/bin/mail
Default compress command: /bin/gzip
Default uncompress command: /bin/gunzip
Default compress extension: .gz
Default state file path: /var/lib/logrotate/status
The state file records when each log was last rotated. A normal run will not rotate the same log more than once in a day unless the rule is size-based or you explicitly force it. For this test, use an alternate state file in the temporary directory. That avoids touching /var/lib/logrotate/status and avoids needing root.
Checkpoint: confirm that the reported version is the one you are documenting. Option names and defaults can differ between releases.
2. Create an isolated log and policy
Create a disposable directory and a log with enough content to exceed a deliberately small threshold. This changes only /tmp:
$ work=/tmp/logrotate-guide
$ rm -rf "$work"
$ mkdir -p "$work/logs" "$work/archive"
$ printf '%s\n' 'test event for logrotate' > "$work/logs/application.log"
$ for n in 1 2 3 4 5 6 7 8 9 10; do
printf 'event %s: this line makes the test log larger\n' "$n" >> "$work/logs/application.log"
done
The cleanup command is safe here because work is an explicit disposable path. Do not adapt it by substituting a broad directory or a production log location.
Now create $work/logrotate.conf with one log definition:
$work/logs/application.log {
size 100
rotate 3
compress
delaycompress
missingok
notifempty
olddir $work/archive
create 0640 nobody nogroup
}
Replace $work with the actual path in the file. Logrotate does not perform shell variable expansion in configuration files, so the literal path must be present. The size rule rotates when the file is larger than 100 bytes. rotate 3 retains three numbered archives before older ones are removed. compress uses gzip, while delaycompress leaves the newest rotated archive uncompressed until the following rotation.
missingok makes a missing test log non-fatal, and notifempty avoids rotating an empty one. olddir moves archives into the separate directory. It normally has to be on the same physical device as the original log. The create line recreates the active log with the specified mode and ownership after rotation. Check that the named account and group exist before using the same line on a real host.
Checkpoint: inspect the finished file and make sure its path is absolute:
$ sed -n '1,20p' "$work/logrotate.conf"
$ test -s "$work/logs/application.log" && echo 'test log is ready'
test log is ready
3. Dry-run the policy
Use debug mode before allowing any change. The -d option prints decisions, makes no log changes, and does not update the state file:
$ logrotate --debug --verbose --state "$work/status" "$work/logrotate.conf"
reading config file ...
Handling 1 logs
rotating pattern: ...
considering log ...
The paths and diagnostic wording vary, so treat the excerpt as a shape rather than a promise of exact output. The important result is that the configuration parses, the log is considered, and the output says debug mode is active. Confirm that the log and archive directory are unchanged:
$ test -s "$work/logs/application.log" && echo 'active log unchanged'
active log unchanged
$ find "$work/archive" -maxdepth 1 -type f -print
$ test ! -e "$work/status" && echo 'state file not written'
state file not written
If the debug output reports an unknown user or group, fix the create line before proceeding. If a path is missing, prefer correcting the policy over adding missingok blindly: that directive is useful for optional logs but can hide a broken path.
4. Apply one rotation and verify the files
This is the first command that changes the temporary log and writes the temporary state file:
$ logrotate --verbose --state "$work/status" "$work/logrotate.conf"
rotating pattern: ...
creating new ... mode = 0640 uid = ... gid = ...
$ find "$work" -maxdepth 2 -type f -printf '%P\n' | sort
archive/application.log.1
logs/application.log
status
With delaycompress, the first archive is normally named application.log.1 without .gz. The active file is recreated, so a service that opens the path by name can continue writing. This example does not signal a service because the test log is not owned by one.
Do not use --force as a routine test switch on production logs. It bypasses the normal time decision and can create an unexpected extra rotation. If you do force a real rotation, treat it as a service operation and verify the application's reopen or reload procedure first.
Run the same command again to see the state check. It should not rotate this size-based log a second time merely because the command was repeated after the first rotation: the new active file is below the threshold. For a time-based rule, the once-per-day state check is more prominent. Inspect the state rather than deleting it to make a rule appear due.
5. Add a service reload only when required
There are two common rotation strategies. The normal rename-and-create operation gives a daemon a new inode, so the daemon must close and reopen its log. Add a postrotate block only after you have verified the service's documented reload signal:
postrotate
/usr/bin/systemctl reload example-service.service
endscript
This command is illustrative, not a command to paste unchanged. Replace the unit with the service that owns the log, and test the reload separately. A failing script can prevent later actions for the affected log, and a reload can disrupt service behaviour. Do not put arbitrary shell text in a rotation policy without reviewing its quoting, executable path and privileges.
If the program cannot reopen its file, copytruncate keeps the original path and truncates it after making a copy. That avoids a daemon reload but introduces a small interval in which log data can be lost. Choose it because the application requires it, not because it is shorter to type. It also makes create ineffective for that log.
6. Put a real policy into service carefully
On a packaged installation, the usual entry point is the system configuration, commonly /etc/logrotate.conf, with additional rules included from a directory. The include directive reads files in the place where it appears, and directory entries are processed alphabetically except for non-regular files and taboo names. Keep a site-specific rule in the directory convention used by your distribution, and check the existing file before adding an include.
Configuration files must not be group-writable or world-writable. A root-run logrotate can execute prerotate and postrotate scripts as root, so a writable rule or directory can become a privilege boundary. For logs in directories controlled by non-privileged users, use su user group where the ownership and service design permit it. Check the resulting permissions with stat before enabling the rule.
Systemd installations may apply ProtectSystem=full to the logrotate service. That can prevent changes under /etc and /usr. Do not weaken the unit merely to make a questionable log path work; move the log or review the service policy with the system owner.
Done means
- You recorded the installed logrotate version and checked the local defaults.
- Your policy names one exact log path and uses an isolated state file during testing.
- Debug mode parsed the policy without changing the log or state.
- A real test rotation produced a new active file and a bounded archive.
- You understand whether the owning service needs a reload or
copytruncate. - Any production change has a reviewed rollback: remove the added rule, restore the previous configuration, and rerun debug mode before the next scheduled rotation.