Logs grow until they fill the disk unless something rotates and deletes them, and many compliance frameworks also require you to keep them for a minimum period. On Ubuntu 24.04 two systems handle this: logrotate for the plain text files in /var/log, and systemd-journald for the binary journal. In this tutorial you will review the defaults, define a retention policy, write a logrotate configuration for your own application, cap the size and age of the journal, and test everything before the next scheduled run.

Prerequisites

To follow this tutorial you need:

  • A server running Ubuntu 24.04 LTS, for example a CubePath VPS.
  • A non-root user with sudo privileges.
  • An application that writes log files to disk. This guide uses a hypothetical app called myapp that writes to /var/log/myapp/ and runs as the myapp system user under a systemd service called myapp.service.

Step 1 - Deciding on a retention policy

Before editing any file, decide how long each type of log must be kept. Retention is a balance between what regulations or your incident response process need and the principle of not keeping personal data longer than necessary.

RequirementWhat it asks for
PCI DSS 4.0 (req. 10.5.1)Keep audit logs for at least 12 months, with the last 3 months immediately available for analysis
GDPR (art. 5)No fixed period: keep logs containing personal data (IP addresses, emails) only as long as the purpose requires
Incident responseEnough history to investigate an intrusion that is detected late, often 90 days or more

A practical split for many servers is: keep web access logs 14 to 30 days on the server, keep authentication and audit logs 90 days on the server, and ship anything that must be kept for a year to a central log server or object storage. Local rotation handles the first part; this tutorial focuses on it.

Step 2 - Understanding how logrotate runs

logrotate is installed by default. On Ubuntu 24.04 it is triggered by a systemd timer, not by cron. Check the timer:

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

The timer runs logrotate /etc/logrotate.conf once a day. That file sets global defaults and then includes every file in /etc/logrotate.d/, where each package drops its own policy:

ls /etc/logrotate.d/
alternatives  apport  apt  bootlog  btmp  dpkg  nginx  rsyslog  ubuntu-advantage-tools  ufw  unattended-upgrades  wtmp

Look at the policy for the system logs written by rsyslog (/var/log/syslog, /var/log/auth.log and others):

cat /etc/logrotate.d/rsyslog

It rotates weekly and keeps 4 old files, so auth.log is kept for about a month by default. If your policy requires 90 days, change rotate 4 to rotate 13 in that file. Package upgrades will ask before overwriting a file you changed.

logrotate records when it last rotated each file in /var/lib/logrotate/status. That is how it knows whether a daily or weekly file is due.

Step 3 - Writing a policy for your application

Create a dedicated file for your application's logs:

sudo nano /etc/logrotate.d/myapp
/var/log/myapp/*.log {
    daily
    rotate 30
    maxage 30
    missingok
    notifempty
    compress
    delaycompress
    dateext
    dateformat -%Y%m%d
    create 0640 myapp adm
    su myapp adm
    sharedscripts
    postrotate
        systemctl kill -s HUP myapp.service >/dev/null 2>&1 || true
    endscript
}

What each directive does:

  • daily and rotate 30: rotate once a day and keep 30 old files, which gives 30 days of history.
  • maxage 30: also delete rotated files older than 30 days, even if the count has not been reached (useful after gaps in rotation).
  • missingok and notifempty: do not fail if the log is missing, and skip rotation of empty files.
  • compress and delaycompress: gzip old logs, but leave the most recent rotated file uncompressed for one cycle, because some programs keep writing to it briefly.
  • dateext and dateformat: name rotated files app.log-20260925 instead of app.log.1, which makes it easy to find a given day.
  • create 0640 myapp adm: recreate an empty log with these permissions and owner after rotating. Group adm lets administrators read logs without sudo.
  • su myapp adm: run the rotation as that user and group. logrotate refuses to work in a directory writable by a non-root user unless this is set.
  • postrotate: tell the application to reopen its log file. sharedscripts makes this run once, not once per matched file.

The postrotate command only works if your application reopens its log files on SIGHUP. If it cannot, replace create, sharedscripts and the postrotate block with copytruncate. logrotate then copies the log and truncates the original in place. It works with any program, but lines written between the copy and the truncate are lost, so prefer the signal approach when the application supports it.

Step 4 - Testing the configuration

Never wait for the nightly run to find out a policy is broken. Run logrotate in debug mode against your file. Debug mode shows what would happen without changing anything:

sudo logrotate --debug /etc/logrotate.d/myapp
reading config file /etc/logrotate.d/myapp
...
rotating pattern: /var/log/myapp/*.log  after 1 days (30 rotations)
empty log files are not rotated, old logs are removed
switching euid from 0 to 999 and egid from 0 to 4
considering log /var/log/myapp/app.log
  Now: 2026-09-25 14:10
  Last rotated at 2026-09-25 14:00
  log does not need rotating (log has been already rotated)

Fix any error: line before continuing. Then force a real rotation once to confirm that permissions and the reload work:

sudo logrotate --force /etc/logrotate.d/myapp
ls -l /var/log/myapp/
-rw-r----- 1 myapp adm    0 Sep 25 14:12 app.log
-rw-r----- 1 myapp adm 5321 Sep 25 14:12 app.log-20260925

The new app.log should be empty with the expected owner, and your application should be writing to it again. Check with tail -f /var/log/myapp/app.log while you generate some traffic.

Step 5 - Rotating by size or more often than daily

Some logs grow too fast for a daily rotation. Add maxsize to rotate early when a file exceeds a size, in addition to the time schedule:

/var/log/myapp/*.log {
    daily
    maxsize 500M
    rotate 30
    ...
}

maxsize is only evaluated when logrotate runs, which is once a day by default. To rotate large logs as soon as they cross the limit, run logrotate every hour by overriding the timer:

sudo systemctl edit logrotate.timer

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

[Timer]
OnCalendar=
OnCalendar=hourly

Reload and confirm the new schedule:

sudo systemctl daemon-reload
systemctl list-timers logrotate.timer

The NEXT column should now show the next full hour. Files marked daily or weekly are still rotated only once per day or week, because logrotate checks the status file; the hourly run only makes maxsize and hourly policies effective.

Step 6 - Setting journald retention

Services managed by systemd also log to the journal in /var/log/journal/. By default journald uses up to 10% of the filesystem (capped at 4 GB) and has no time limit. Check the current usage:

journalctl --disk-usage
Archived and active journals take 1.2G in the file system.

Set explicit limits with a drop-in file:

sudo mkdir -p /etc/systemd/journald.conf.d
sudo nano /etc/systemd/journald.conf.d/retention.conf
[Journal]
SystemMaxUse=1G
SystemKeepFree=2G
MaxRetentionSec=90day

SystemMaxUse caps the total size, SystemKeepFree makes journald stop growing when free disk space falls under that value, and MaxRetentionSec deletes entries older than 90 days. Restart journald to apply it:

sudo systemctl restart systemd-journald

To free space immediately down to the new limits, vacuum the archived journals:

sudo journalctl --vacuum-size=1G --vacuum-time=90d

Run journalctl --disk-usage again to confirm the size dropped, and journalctl --list-boots | head -n 3 to see how far back the oldest entries go.

Troubleshooting

  • error: skipping "/var/log/myapp/app.log" because parent directory has insecure permissions: the directory is writable by a user or group other than root. Add a su line with the owning user and group, as in Step 3.
  • Rotated files keep growing and the new log stays empty: the application still has the old file open. Either make the postrotate signal work or switch to copytruncate. sudo lsof +L1 | grep myapp shows deleted files still held open.
  • Rotation never happens: check sudo journalctl -u logrotate.service for errors from the last run. A syntax error in any file under /etc/logrotate.d/ can stop the whole run, so test with sudo logrotate --debug /etc/logrotate.conf.

Conclusion

You now have a written retention policy, a tested logrotate configuration for your application, and a journal with a size and age limit, so logs can no longer fill the disk and are kept exactly as long as you decided. Next, consider shipping logs that must be kept longer to a central server with rsyslog or a log platform, protecting security-relevant events with auditd, and removing personal data such as full IP addresses from logs you retain for a long time.