Skip to content

How to rotate an application's logs

An application writes its own log file, /var/log/myapp/myapp.log, which grows until it fills the disk. This guide adds a logrotate rule that rotates the file every day, compresses the old copies and deletes them after two weeks, tests the rule, and makes sure logrotate runs regularly. Replace myapp and the path with your application's.

Before you begin

  • You need a shell with sudo on the machine.
  • Find out whether the application can be told to reopen its log file, for example with a signal (often SIGHUP or SIGUSR1) or a reload command. The rule below works for any application; Reopen the file instead of copying it shows the better variant for applications that can.
  • Check what already rotates the file: grep -r myapp /etc/logrotate.d/ should print nothing, since two rules for one file make logrotate report an error.

Steps

  1. Make sure logrotate is installed and runs every day.

    Standard Debian and Ubuntu installations include logrotate; only the Debian 13 cloud image doesn't, and apt-get leaves an existing installation as it is. Installing the package also enables a systemd timer that runs it daily:

    sudo apt-get update
    sudo apt-get install -y logrotate
    systemctl list-timers logrotate.timer
    

    logrotate is installed by default and run daily by a systemd timer:

    systemctl list-timers logrotate.timer
    

    Install logrotate, and start its daily timer now (the package links the timer into timers.target, so otherwise it would only start at the next boot):

    sudo pacman -S --needed --noconfirm logrotate
    sudo systemctl enable --now logrotate.timer
    systemctl list-timers logrotate.timer
    

    Install logrotate, and a cron daemon to run it. app-admin/logrotate installs a daily job, /etc/cron.daily/logrotate, which cronie runs through anacron, so it also runs after the machine was switched off at the scheduled time:

    sudo emerge --getbinpkg --noreplace app-admin/logrotate sys-process/cronie
    sudo rc-update add cronie default
    sudo rc-service cronie start
    

    This needs a synchronised ebuild repository (emerge --sync). If another cron daemon is already running, keep it instead of cronie: any that runs /etc/cron.daily will do.

    On the systemd families, the NEXT column of systemctl list-timers shows when logrotate runs next.

  2. Create the rule, /etc/logrotate.d/myapp:

    /var/log/myapp/*.log {
        daily
        rotate 14
        missingok
        notifempty
        compress
        delaycompress
        dateext
        copytruncate
    }
    
    Directive Effect
    daily Rotate once a day
    rotate 14 Keep 14 old copies, and delete older ones
    missingok Don't report an error if no file matches
    notifempty Don't rotate a file that is empty
    compress Compress old copies with gzip
    delaycompress Leave the newest old copy uncompressed until the next rotation, in case the application is still writing to it
    dateext Name old copies after the date, such as myapp.log-20261107, instead of numbering them
    copytruncate Copy the file, then empty the original in place, so the application keeps writing to the same open file

    The pattern *.log matches every file ending in .log in the directory, but not the rotated copies. Don't use * on its own: it would match the old copies too, and rotate them again.

    /etc/logrotate.conf sets global defaults, such as weekly and rotate 4, and reads the files in /etc/logrotate.d/. The rule repeats every setting it needs, so it behaves the same on every distribution and when you test it on its own. See logrotate directives for other directives, such as maxsize to rotate early when the file grows fast.

  3. Test the rule in debug mode, which shows what logrotate would do without changing anything:

    $ sudo logrotate -d /etc/logrotate.d/myapp
    warning: logrotate in debug mode does nothing except printing debug messages!  Consider using verbose mode (-v) instead if this is not what you want.
    
    reading config file /etc/logrotate.d/myapp
    Reading state from file: /var/lib/logrotate/status
    Allocating hash table for state file, size 64 entries
    
    Handling 1 logs
    
    rotating pattern: /var/log/myapp/*.log after 1 days empty log files are not rotated, (14 rotations), old logs are removed
    considering log /var/log/myapp/myapp.log
    Creating new state
      Now: 2026-11-07 14:10
      Last rotated at 2026-11-07 14:00
      log does not need rotating (log has already been rotated)
    

    Check that the considering log lines list the files you expect, and that there are no error: or warning: lines other than the first. A mistyped directive produces only a warning, such as warning: /etc/logrotate.d/myapp:2 unknown option 'dialy' -- ignoring line, and the rest of the rule then runs without it. The first time it sees a file, logrotate only records it in its state file, as if it had just been rotated, so log does not need rotating is normal here. The path of the state file differs between distributions.

  4. Force a rotation once, to see the result without waiting a day:

    $ sudo logrotate -f /etc/logrotate.d/myapp
    $ ls -l /var/log/myapp/
    total 20
    -rw-r--r-- 1 root root     0 Nov  7 14:11 myapp.log
    -rw-r--r-- 1 root root 21392 Nov  7 14:11 myapp.log-20261107
    

    myapp.log is now empty and the application keeps writing to it; the previous content is in myapp.log-20261107. Because of delaycompress, that copy is compressed at the next rotation.

Reopen the file instead of copying it

copytruncate has a drawback: anything the application writes between the copy and the truncation is lost, and the copy costs time and space for a large file. If the application can reopen its log file, let logrotate rename the file, create a new one, and then tell the application to switch to it. Replace copytruncate with create and a postrotate script, for example for a systemd service whose reload reopens its logs:

/var/log/myapp/*.log {
    daily
    rotate 14
    missingok
    notifempty
    compress
    delaycompress
    dateext
    create 0640 myapp myapp
    sharedscripts
    postrotate
        systemctl reload myapp.service >/dev/null 2>&1 || true
    endscript
}

create 0640 myapp myapp creates the new, empty file with that mode, owner and group; use the owner the application runs as. sharedscripts runs the script once for all the files that match, rather than once per file. On Gentoo with OpenRC, use rc-service myapp reload in the script, if the service's init script supports reload.

Verify

Check that the rule rotated the file and that logrotate is scheduled to run:

$ ls /var/log/myapp/
myapp.log  myapp.log-20261107
$ systemctl list-timers logrotate.timer
NEXT                        LEFT LAST PASSED UNIT            ACTIVATES
Sun 2026-11-08 00:00:00 UTC 9h   -    -      logrotate.timer logrotate.service
$ ls /var/log/myapp/
myapp.log  myapp.log-20261107
$ systemctl list-timers logrotate.timer
NEXT                        LEFT LAST PASSED UNIT            ACTIVATES
Sun 2026-11-08 00:00:00 UTC 9h   -    -      logrotate.timer logrotate.service
$ ls /var/log/myapp/
myapp.log  myapp.log-20261107
$ systemctl list-timers logrotate.timer
NEXT                        LEFT LAST PASSED UNIT            ACTIVATES
Sun 2026-11-08 00:00:00 UTC 9h   -    -      logrotate.timer logrotate.service
$ ls /var/log/myapp/
myapp.log  myapp.log-20261107
$ rc-service cronie status
 * status: started
$ ls /etc/cron.daily/
logrotate  man-db  systemd-tmpfiles-clean

The next day, ls /var/log/myapp/ shows a new dated copy, and the previous one compressed with a .gz suffix.

Troubleshooting

error: skipping "/var/log/myapp/myapp.log" because parent directory has insecure permissions
The directory can be written by a group or by everyone, so a non-root user could trick logrotate, which runs as root, into overwriting other files. Add su <USER> <GROUP> to the rule, naming the user and group that own the directory, so that logrotate rotates the files as that user.
logrotate reports a duplicate log entry
Two rules match the same file, for example one shipped by the application's package. Keep one of them.
destination /var/log/myapp/myapp.log-20261107 already exists, skipping rotation
With dateext, a file can be rotated only once a day, because the copy's name contains the date. This happens when you force a second rotation on the same day; it doesn't affect the daily run.
Logs under /etc or /usr aren't rotated by the daily run, although logrotate -f works
On the systemd families, logrotate.service runs with ProtectSystem=full, which makes /usr, /boot, /efi and /etc read-only for it; other locations, such as /var/log, /srv or /opt, are unaffected. Move the logs out of /etc and /usr. On the RHEL family, SELinux also limits logrotate to files with log labels; check with ls -Z, and look for denials with sudo ausearch -m avc -c logrotate.