Write a logrotate config and test it before using it
Write a logrotate config for an app log, preview it with -d, force a run with -f and a state file, and check the result. Covers copytruncate versus postrotate.
Run a new config from a scratch location before it goes into /etc/logrotate.d/. The output below was run on Ubuntu 24.04 with logrotate 3.21.0; only the paths were changed to /var/log/myapp and /etc/logrotate.d/myapp.
[1] Write the config for an app log. This file belongs at /etc/logrotate.d/myapp.
/var/log/myapp/*.log { daily rotate 14 compress delaycompress missingok notifempty copytruncate}| Option | Meaning (per man logrotate) |
|---|---|
daily | Rotate once a day |
rotate 14 | Keep 14 old logs; older ones are removed |
compress | Compress old logs with gzip |
delaycompress | Postpone compressing the just-rotated file to the next rotation |
missingok | If the log file is missing, go on without an error |
notifempty | Do not rotate an empty log |
copytruncate | Copy the file, then truncate the original in place instead of moving it |
[2] Choose between copytruncate and postrotate. If the program can be told to reopen its log file, postrotate is safer; if not, use copytruncate. man logrotate warns that there is a very small time slice between copying the file and truncating it, so some logging data might be lost.
/var/log/myapp/app.log { daily rotate 14 compress delaycompress missingok notifempty create 0640 root root postrotate systemctl reload myapp.service endscript}postrotate runs a script after the file is moved, so the script must make the program reopen its log. The systemctl reload line is an example. Here create makes the new log file; with copytruncate, create has no effect.
[3] Preview with -d. It changes nothing and only prints its decisions; -s names the state file (last rotation time per log). A new state file counts as "just rotated" and prints log does not need rotating. The run below used a state file holding a date two days old (renaming lines omitted).
# logrotate -d -s /tmp/myapp.state /etc/logrotate.d/myappreading config file /etc/logrotate.d/myappReading state from file: /tmp/myapp.stateHandling 1 logs rotating pattern: /var/log/myapp/*.log after 1 days (14 rotations)empty log files are not rotated, old logs are removedconsidering log /var/log/myapp/app.log Now: 2026-10-19 12:17 Last rotated at 2026-10-17 00:00 log needs rotatingconsidering log /var/log/myapp/empty.log ... log does not need rotating (log is empty)...copying /var/log/myapp/app.log to /var/log/myapp/app.log.1truncating /var/log/myapp/app.log[4] Force a run with -f. It rotates regardless of schedule and updates the state file. File sizes (bytes) and names after the first rotation:
# logrotate -f -s /tmp/myapp.state /etc/logrotate.d/myapp# ls -l /var/log/myapp | tail -n +2 | awk '{print $5, $9}'0 app.log14 app.log.10 empty.logapp.log.1 is not compressed yet because of delaycompress. Append a line and force again to compress it.
# echo "line 3" >> /var/log/myapp/app.log# logrotate -f -s /tmp/myapp.state /etc/logrotate.d/myapp# ls -l /var/log/myapp | tail -n +2 | awk '{print $5, $9}'0 app.log7 app.log.131 app.log.2.gz0 empty.log[5] Run the postrotate config the same way. The script runs only on a real rotation, such as one forced with -f. In the test, postrotate contained echo "reopen log" >> postrotate.out; afterwards postrotate.out held reopen log, and app.log was recreated with the permissions given to create (-rw-r-----).
[6] Find out who runs it once a day. On Ubuntu, logrotate.timer starts logrotate.service, which reads /etc/logrotate.conf.
$ systemctl list-timers logrotate.timerThe packaged unit files have OnCalendar=daily and Persistent=true (catch up on a missed run); the service is ExecStart=/usr/sbin/logrotate /etc/logrotate.conf.