Table of Contents
Introduction
Log files are useful until they quietly fill a disk. On Ubuntu servers, logrotate prevents that by rotating, compressing, and removing older file-based logs according to rules you define.
Ubuntu 24.04 LTS still uses the familiar logrotate configuration paths, but there are two important updates for anyone modernizing older tutorials. First, many services now write primarily to systemd-journald, so logrotate only applies to applications that write normal files such as /var/log/example-app/app.log. Second, Ubuntu 24.04 runs logrotate through a systemd timer, which gives you better visibility and cleaner scheduling than older cron-only examples.
In this tutorial, you will inspect Ubuntu's default logrotate setup, create a custom policy for an example application, test the policy safely, and create an optional hourly rotation schedule with a dedicated systemd timer.
Tested Environment
- Ubuntu 24.04 LTS
- logrotate 3.21.0 package series from Ubuntu Noble repositories
- A non-root user with
sudoprivileges - Shell access to the server
What You Will Learn
- Where Ubuntu stores logrotate configuration files
- How Ubuntu 24.04 schedules logrotate runs with
systemd - How to write a custom rotation policy for application logs
- How to test logrotate without changing files
- How to force a rotation for validation
- How to schedule hourly rotation with a dedicated timer
- When to use journald controls instead of logrotate
Prerequisites
You need an Ubuntu 24.04 LTS server with a user that can run commands with sudo. You should also understand which application writes the logs you want to manage and which user owns those log files.
Confirm the operating system version:
lsb_release -ds
Expected output should include Ubuntu 24.04 LTS. Your point-release number may be different:
Ubuntu 24.04 LTS
Confirm that logrotate is installed and check the version:
logrotate --version
You should see output that starts with a version line similar to this:
logrotate 3.21.0
...
If the command is missing, install logrotate from Ubuntu's package repository:
sudo apt update
sudo apt install logrotate
Understand What Logrotate Does And Does Not Manage
Logrotate manages files. It does not manage every log visible on the system.
On Ubuntu 24.04 LTS, many system services send logs to systemd-journald. You view those logs with journalctl, and you manage journal storage with journald settings, not with logrotate. For example, this command shows how much disk space the journal currently uses:
journalctl --disk-usage
Logrotate is the right tool when an application writes log files such as these:
/var/log/nginx/access.log
/var/log/nginx/error.log
/var/log/example-app/app.log
/home/deploy/apps/example-app/log/production.log
If your application only logs to journald, use journalctl, /etc/systemd/journald.conf, and files under /etc/systemd/journald.conf.d/ instead of creating a logrotate rule.
Inspect Ubuntu's Default Logrotate Schedule
Ubuntu 24.04 runs logrotate with a systemd timer. Check whether the timer is enabled:
systemctl status logrotate.timer --no-pager
You can also ask systemd when the timer will run next:
systemctl list-timers logrotate.timer --no-pager
Inspect the service that the timer starts:
systemctl cat logrotate.service
The service normally runs /usr/sbin/logrotate /etc/logrotate.conf. That main configuration file includes additional per-package rules from /etc/logrotate.d/.
Check the main configuration file:
sudo cat /etc/logrotate.conf
List the package-specific configuration files:
ls -1 /etc/logrotate.d/
View an existing rule, such as the package manager's logrotate policy:
sudo cat /etc/logrotate.d/apt
The exact file contents can vary by package version, but the pattern is consistent: one or more log paths followed by a block of options that define how often to rotate, how many old files to keep, whether to compress them, and whether to skip empty or missing files.
Create A Custom Application Log Directory
For this tutorial, assume an application named example-app writes file-based logs under /var/log/example-app/ and runs as the www-data user.
Create the log directory with controlled ownership and permissions:
sudo install -d -o www-data -g adm -m 0750 /var/log/example-app
Create a sample application log file:
sudo install -o www-data -g adm -m 0640 /dev/null /var/log/example-app/app.log
Add a test line so there is content to rotate:
echo "$(date --iso-8601=seconds) example log entry" | sudo tee -a /var/log/example-app/app.log >/dev/null
Check the result:
sudo ls -l /var/log/example-app/
You should see app.log owned by www-data with the adm group.
Create A Logrotate Rule For The Application
Create a new configuration file under /etc/logrotate.d/:
sudo nano /etc/logrotate.d/example-app
Add this configuration:
/var/log/example-app/*.log {
daily
missingok
rotate 14
compress
delaycompress
dateext
dateyesterday
notifempty
create 0640 www-data adm
su www-data adm
sharedscripts
postrotate
systemctl reload example-app.service 2>/dev/null || systemctl kill -s HUP example-app.service 2>/dev/null || true
endscript
}
This rule rotates matching .log files daily, keeps 14 rotated files, compresses older rotations, uses date-based names, skips empty logs, creates replacement files with safe permissions, and reloads the application after rotation so it can reopen its log file.
The su www-data adm line is important on modern systems. When logrotate rotates logs in a directory controlled by a non-root user or group, this directive tells logrotate which user and group to use for the file operation. Without it, logrotate may refuse to rotate the file because the parent directory permissions look unsafe.
The delaycompress line is also intentional. It waits until the next rotation before compressing the most recently rotated file. This helps with applications that briefly keep writing to the previous file before they reopen the new one.
The dateext and dateyesterday lines create rotated names with dates that match the day the log entries were written. This is often easier to audit than numeric suffixes such as .1 and .2.
The notifempty line prevents logrotate from creating empty rotated archives when the current log file has no content.
For high-volume applications, you can combine a time-based rule with a size limit such as maxsize 100M. That lets logrotate rotate daily during normal traffic, while still rotating earlier if a log grows too quickly.
The postrotate script first tries systemctl reload, which is safest when the service unit defines a proper reload action. If no reload action exists, it falls back to sending SIGHUP to the service cgroup. For multi-process services, confirm how your application handles reloads before using this pattern in production.
Test The Rule Without Changing Files
Before forcing a real rotation, run logrotate in debug mode:
sudo logrotate --debug /etc/logrotate.d/example-app
Debug mode prints what logrotate would do, but it does not rotate files and does not update the state file. Review the output for errors about permissions, missing users, missing paths, or invalid directives.
You can also test the full Ubuntu configuration, including all files included by /etc/logrotate.conf:
sudo logrotate --debug /etc/logrotate.conf
Force A Rotation For Validation
Once the debug output looks correct, force one rotation.
Note: The next command rotates matched files immediately and updates logrotate's state. Use it only with the example log path created in this tutorial, or during a maintenance window for real application logs.
sudo logrotate --force --verbose /etc/logrotate.d/example-app
List the application log directory again:
sudo ls -l /var/log/example-app/
You should see a new app.log file and at least one rotated file with a dated suffix, such as app.log-20260512, or a compressed older rotation after later runs.
Check whether logrotate recorded the file in its state database:
sudo grep example-app /var/lib/logrotate/status
If the command returns an entry for /var/log/example-app/app.log, logrotate has tracked the file successfully.
Check The Systemd Timer Logs
After the normal daily timer runs, view its logs with journalctl:
journalctl -u logrotate.service --since today --no-pager
If the timer has not run yet, you can start the service manually:
sudo systemctl start logrotate.service
Then check the service status:
systemctl status logrotate.service --no-pager
Create An Optional Hourly Rotation Timer
The hourly directive inside a file under /etc/logrotate.d/ is not enough by itself. Logrotate only rotates hourly if something runs logrotate hourly.
If one application needs hourly rotation, create a separate configuration file instead of reusing the daily /etc/logrotate.d/example-app rule. This avoids rotating the same file from two schedules.
Create the hourly configuration:
sudo nano /etc/logrotate-example-app-hourly.conf
Add this rule:
/var/log/example-app/*.log {
hourly
missingok
rotate 24
compress
delaycompress
dateext
dateformat -%Y%m%d-%H
notifempty
create 0640 www-data adm
su www-data adm
sharedscripts
postrotate
systemctl reload example-app.service 2>/dev/null || systemctl kill -s HUP example-app.service 2>/dev/null || true
endscript
}
The dateformat -%Y%m%d-%H line gives each hourly rotation a unique timestamp such as app.log-20260512-14. Without hour precision, multiple rotations on the same day could try to reuse the same dated filename.
Create a dedicated systemd service:
sudo nano /etc/systemd/system/example-app-logrotate.service
Add this service definition:
[Unit]
Description=Rotate Example App Logs
[Service]
Type=oneshot
ExecStart=/usr/sbin/logrotate --state /var/lib/logrotate/example-app-hourly.status /etc/logrotate-example-app-hourly.conf
Create the matching timer:
sudo nano /etc/systemd/system/example-app-logrotate.timer
Add this timer definition:
[Unit]
Description=Run Example App Logrotate Hourly
[Timer]
OnCalendar=hourly
Persistent=true
Unit=example-app-logrotate.service
[Install]
WantedBy=timers.target
Reload systemd and enable the timer:
sudo systemctl daemon-reload
sudo systemctl enable --now example-app-logrotate.timer
Confirm the timer is active:
systemctl list-timers example-app-logrotate.timer --no-pager
You can test the service immediately:
sudo systemctl start example-app-logrotate.service
Then check its logs:
journalctl -u example-app-logrotate.service --since today --no-pager
Do not keep both the daily /etc/logrotate.d/example-app rule and the independent hourly rule active for the same log files. Choose one schedule for each log path.
Troubleshooting
This usually means the log directory is writable by a non-root user or group and the rule does not include su user group. Add a matching su directive and make sure the create ownership matches the application user and group.
Debug mode does not change files. If the output says the log does not need rotating, logrotate may be respecting the state file and the daily, weekly, or monthly schedule. Use --force --verbose for a one-time validation run.
The application may need a signal or reload after rotation. Prefer a service-specific reload or hangup signal. If the application cannot reopen logs, copytruncate can be used as a fallback, but it can lose a small amount of log data during the copy-and-truncate window.
The hourly directive only defines eligibility. A timer or scheduler must run logrotate hourly. On Ubuntu 24.04, use a dedicated systemd timer for custom hourly jobs.
That is separate from logrotate. Check journal disk usage with:
journalctl --disk-usage
For persistent limits, configure journald settings such as SystemMaxUse= in /etc/systemd/journald.conf or a drop-in file under /etc/systemd/journald.conf.d/.
Security Notes
- Keep log files readable only by users and groups that need them. Many logs contain IP addresses, usernames, tokens, request paths, or error traces.
- Avoid world-writable log directories. Logrotate refuses some unsafe directory layouts for good reason.
- Be careful with
copytruncate; it is convenient for applications that cannot reopen logs, but it can lose log lines. - Treat rotated logs as sensitive data. Compression reduces disk usage, but it does not protect confidentiality.
- If logs must be retained for compliance, define retention separately from rotation.
rotate 14is a storage policy, not a legal retention policy.
Cleanup
If you created the example daily rule and no longer need it, remove it:
sudo rm /etc/logrotate.d/example-app
If you created the optional hourly timer, disable and remove it:
sudo systemctl disable --now example-app-logrotate.timer
sudo rm /etc/systemd/system/example-app-logrotate.timer
sudo rm /etc/systemd/system/example-app-logrotate.service
sudo rm /etc/logrotate-example-app-hourly.conf
sudo systemctl daemon-reload
Only remove the example log directory if it does not contain real application logs. This command permanently deletes the directory and everything under it:
sudo rm -rf /var/log/example-app
Conclusion
You inspected how Ubuntu 24.04 LTS runs logrotate, created a custom rotation rule for application log files, tested it safely, forced a validation rotation, and created an optional hourly systemd timer. This gives you a repeatable pattern for managing file-based application logs while keeping journald-managed logs in their own lane.