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
sudoprivileges. - An application that writes plain log files. The examples use a service called
myappthat 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:
| Path | Purpose |
|---|---|
/etc/logrotate.conf | Global defaults and the include of the directory below |
/etc/logrotate.d/ | One file per package or application with its own rules |
/var/lib/logrotate/status | State 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:
| Directive | Effect |
|---|---|
daily, weekly, monthly | Rotation frequency |
size 100M | Rotate only when the file exceeds the size, ignoring the frequency |
maxsize 100M | Rotate at the scheduled time or earlier if the size is exceeded |
rotate 14 | Number of old files to keep; older ones are deleted |
maxage 30 | Delete rotated files older than 30 days |
compress / delaycompress | Gzip old files; delaycompress leaves the most recent one uncompressed |
missingok | Do not fail if the log does not exist |
notifempty | Skip rotation when the file is empty |
create 0640 user group | Create a new empty log with this mode and owner after rotating |
copytruncate | Copy the log and truncate the original instead of renaming it |
dateext | Use a date suffix (app.log-20260925) instead of .1, .2 |
sharedscripts | Run postrotate once for all matched files, not once per file |
postrotate ... endscript | Shell commands run after rotating, usually to reopen logs |
su user group | Rotate 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.
