Logrotate renames, compresses and deletes old log files on a schedule so that /var/log never fills the disk. Ubuntu ships it preinstalled and already rotates the logs of system packages such as Nginx, rsyslog and apt. In this tutorial you will learn how the default setup works on Ubuntu 24.04, write a rotation policy for your own application, make the application reopen its log file after rotation, and test the rule before relying on it.

Prerequisites

To follow this guide you need:

  • A server running Ubuntu 24.04 LTS, for example a CubePath VPS. The same files and commands apply to Debian 12.
  • A non-root user with sudo privileges.
  • An application that writes plain log files. The examples use a service called myapp that writes to /var/log/myapp/; replace the names with your own.

Step 1 - Checking how logrotate runs

On Ubuntu 24.04, logrotate is started once a day by a systemd timer, not by cron. Confirm that the package is installed and the timer is active:

logrotate --version
systemctl list-timers logrotate.timer
logrotate 3.21.0
...
NEXT                        LEFT     LAST                        PASSED  UNIT            ACTIVATES
Thu 2026-09-26 00:00:00 UTC 9h left  Wed 2026-09-25 00:00:04 UTC 14h ago logrotate.timer logrotate.service

Each run executes logrotate.service, and its output goes to the journal. Check the last run for errors:

journalctl -u logrotate.service -n 20 --no-pager

A file /etc/cron.daily/logrotate may also exist, but it exits immediately on systemd systems, so the timer is the only scheduler that matters.

Step 2 - Understanding the configuration files

Logrotate reads three locations:

PathPurpose
/etc/logrotate.confGlobal defaults and the include of the directory below
/etc/logrotate.d/One file per package or application with its own rules
/var/lib/logrotate/statusState file with the date each log was last rotated

Open the global file to see the defaults:

cat /etc/logrotate.conf
weekly
su root adm
rotate 4
create
#dateext
#compress
include /etc/logrotate.d

These settings apply to every rule that does not override them: rotate weekly, keep four old copies and create an empty log after rotating. Package rules in /etc/logrotate.d/ usually override them. For example, the Nginx rule installed by the nginx package looks like this:

cat /etc/logrotate.d/nginx
/var/log/nginx/*.log {
	daily
	missingok
	rotate 14
	compress
	delaycompress
	notifempty
	create 0640 www-data adm
	sharedscripts
	prerotate
		if [ -d /etc/logrotate.d/httpd-prerotate ]; then \
			run-parts /etc/logrotate.d/httpd-prerotate; \
		fi \
	endscript
	postrotate
		invoke-rc.d nginx rotate >/dev/null 2>&1
	endscript
}

Leave package files as they are unless you need different retention: your changes can conflict with future package upgrades, which ask whether to keep your version.

Step 3 - Learning the directives you will use

A rule is a path (wildcards allowed) followed by directives in braces. These are the ones that matter in almost every configuration:

DirectiveEffect
daily, weekly, monthlyRotation frequency
size 100MRotate only when the file exceeds the size, ignoring the frequency
maxsize 100MRotate at the scheduled time or earlier if the size is exceeded
rotate 14Number of old files to keep; older ones are deleted
maxage 30Delete rotated files older than 30 days
compress / delaycompressGzip old files; delaycompress leaves the most recent one uncompressed
missingokDo not fail if the log does not exist
notifemptySkip rotation when the file is empty
create 0640 user groupCreate a new empty log with this mode and owner after rotating
copytruncateCopy the log and truncate the original instead of renaming it
dateextUse a date suffix (app.log-20260925) instead of .1, .2
sharedscriptsRun postrotate once for all matched files, not once per file
postrotate ... endscriptShell commands run after rotating, usually to reopen logs
su user groupRotate as this user and group instead of root

Size checks only happen when logrotate runs, so with the default daily timer size 100M means "at most once a day, if the file is larger than 100 MB".

Step 4 - Creating a rotation rule for your application

Suppose myapp runs as the myapp user and writes /var/log/myapp/app.log and /var/log/myapp/error.log. Create a dedicated file for it:

sudo nano /etc/logrotate.d/myapp

Add the following rule:

/var/log/myapp/*.log {
    daily
    rotate 14
    maxsize 200M
    compress
    delaycompress
    missingok
    notifempty
    dateext
    su myapp myapp
    create 0640 myapp myapp
    sharedscripts
    postrotate
        pkill -HUP -x myapp || true
    endscript
}

This keeps two weeks of daily logs, rotates earlier if a file grows past 200 MB, and compresses everything except the most recent rotated file. The su myapp myapp line is needed because /var/log/myapp is owned by myapp: logrotate refuses to work in a directory that is writable by a non-root user unless you tell it which user to act as.

The postrotate block sends SIGHUP to processes named exactly myapp so they close the renamed file and open a new app.log. Replace it with the reload mechanism your application actually documents. Because of su, the script runs with the myapp identity, which is allowed to signal its own processes; a command that needs root (such as systemctl reload) would fail here. The || true keeps logrotate from reporting an error when the application is stopped.

Save the file. Logrotate needs rule files to be owned by root and not writable by others, which is the default when you create them with sudo.

When the application cannot reopen its log

Some programs keep the file open forever and have no reload signal, so after a rename they keep writing to the rotated file. For those, replace create and the postrotate block with copytruncate:

/var/log/legacyapp/output.log {
    daily
    rotate 7
    compress
    delaycompress
    missingok
    notifempty
    copytruncate
}

Logrotate copies the file and then truncates the original in place. The trade-off is that lines written between the copy and the truncate are lost, so prefer create plus a reload whenever the application supports it.

Step 5 - Testing the rule

Run logrotate in debug mode against your file. Debug mode shows what would happen without touching any log:

sudo logrotate -d /etc/logrotate.d/myapp
reading config file /etc/logrotate.d/myapp
...
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/app.log
...
  log does not need rotating (log has been already rotated)

Syntax errors such as an unknown directive or a missing brace appear at the top of this output. When the rule parses cleanly, force a real rotation to check permissions and the reload command:

sudo logrotate -vf /etc/logrotate.d/myapp

Then list the directory:

ls -l /var/log/myapp/
-rw-r----- 1 myapp myapp    0 Sep 25 14:05 app.log
-rw-r----- 1 myapp myapp 8211 Sep 25 14:05 app.log-20260925
-rw-r----- 1 myapp myapp    0 Sep 25 14:05 error.log
-rw-r----- 1 myapp myapp  512 Sep 25 14:05 error.log-20260925

The rotated files are not compressed yet because of delaycompress; they will be gzipped on the next run. Generate some activity in the application and confirm that new lines go to app.log, not to the dated file. If they still go to the old file, the reload in postrotate is not working.

Finally, check that the state file recorded the rotation:

sudo grep myapp /var/lib/logrotate/status
"/var/log/myapp/app.log" 2026-9-25-14:5:0
"/var/log/myapp/error.log" 2026-9-25-14:5:0

Step 6 - Rotating more often than once a day (optional)

The hourly directive only works if logrotate itself runs every hour. To change the schedule, create a drop-in for the timer instead of editing the packaged unit:

sudo systemctl edit logrotate.timer

Add these lines in the editor. The empty OnCalendar= clears the packaged daily schedule before setting the new one:

[Timer]
OnCalendar=
OnCalendar=hourly

Save and confirm the next run time:

systemctl list-timers logrotate.timer

The next run should now be at the top of the next hour. Rules with daily or weekly are not affected: logrotate uses the state file to decide whether each log is due.

Troubleshooting

"skipping ... because parent directory has insecure permissions": the log directory is writable by a user or group other than root. Add a su user group line with the owner of the directory, as in Step 4. Do not loosen the directory permissions to fix it.

The log is not rotating: run sudo logrotate -d /etc/logrotate.d/yourfile and read the reason for each file. Common causes are notifempty on an empty file, a size threshold that has not been reached, or the state file showing it was already rotated in the current period.

The application keeps writing to the rotated file: the postrotate command did not make the process reopen its log. Verify the signal or reload command by running it by hand, or switch to copytruncate.

A rule in /etc/logrotate.d/ is ignored: logrotate skips files with names that match its tabooext list, such as .dpkg-old or .dpkg-dist, and files that are group or world writable. Keep the file name plain and its mode 0644.

Logs from the systemd journal keep growing: the journal is not managed by logrotate. Limit it with SystemMaxUse= in /etc/systemd/journald.conf instead.

Conclusion

You now know how Ubuntu 24.04 schedules logrotate, how to write a rule with retention, compression and a safe reload, and how to test it with -d and -f before it runs unattended. As next steps, review the retention of noisy package logs in /etc/logrotate.d/, cap the systemd journal size in journald.conf, and ship important logs to a central system so that local rotation is not your only copy.