Log files are the primary diagnostic tool for server administrators — but without rotation they become a problem in their own right. An unrotated /var/log/nginx/access.log on a busy server can grow to tens of gigabytes within weeks, filling the filesystem, crashing the logging application, and making the log itself unusable because no tool can efficiently process a 50 GB text file. logrotate solves this by periodically archiving log files: renaming the current file, optionally compressing the archive, creating a fresh empty log file, and deleting archives beyond a configured retention period. On RHEL 9, logrotate is installed by default and runs daily via a systemd timer (or cron job), managing logs for dozens of system services out of the box. This guide covers understanding the existing configuration, writing custom rules for application logs, testing without rotating, debugging common problems, and configuring advanced options like per-size rotation and pre/post-rotate scripts.
Prerequisites
- RHEL 9 server with root or sudo access
logrotateinstalled (included by default)
Step 1 — Verify logrotate is Installed and Running
# Confirm installation
rpm -q logrotate
# Check the systemd timer
systemctl list-timers logrotate*
# Or check the legacy cron job
cat /etc/cron.daily/logrotate
On RHEL 9, logrotate runs as a systemd timer named logrotate.timer that triggers daily.
Step 2 — Understand the Configuration File Hierarchy
/etc/logrotate.conf— main configuration file with global defaults/etc/logrotate.d/— drop-in directory; one file per application. All files in this directory are included by logrotate automatically.
View the global defaults:
cat /etc/logrotate.conf
Common global settings:
# Rotate weekly
weekly
# Keep 4 rotated files
rotate 4
# Create new empty log files after rotation
create
# Use date as suffix instead of sequential numbers (e.g. access.log-20250519)
dateext
# Compress rotated files with gzip
compress
# Include drop-in files
include /etc/logrotate.d
Step 3 — View Existing Application Configurations
ls /etc/logrotate.d/
cat /etc/logrotate.d/nginx
A typical Nginx logrotate configuration:
/var/log/nginx/*.log {
daily
missingok
rotate 52
compress
delaycompress
notifempty
create 0640 nginx adm
sharedscripts
postrotate
if [ -f /run/nginx.pid ]; then
kill -USR1 $(cat /run/nginx.pid)
fi
endscript
}
Step 4 — Write a Custom logrotate Configuration
Create a configuration for a custom application that writes to /var/log/myapp/:
vi /etc/logrotate.d/myapp
/var/log/myapp/*.log {
# Rotate daily
daily
# Keep 30 days of archives
rotate 30
# Compress rotated files
compress
# Delay compression by one rotation cycle (so the current log is not compressed)
# This allows the application to finish writing before compressing
delaycompress
# Do not error if log file is missing
missingok
# Do not rotate if the file is empty
notifempty
# Create a new log file after rotation with specified permissions
create 0640 appuser appgroup
# Use date suffix (e.g., myapp.log-20250519)
dateext
dateformat -%Y%m%d
# Run pre/postrotate scripts once even if multiple log files match
sharedscripts
# Commands to run before rotation begins
prerotate
# Custom pre-rotation action (optional)
echo "Starting log rotation for myapp" >> /var/log/myapp/rotate.log
endscript
# Commands to run after rotation completes
postrotate
# Signal the application to reopen its log files
# For systemd-managed services:
systemctl kill --kill-who=main --signal=HUP myapp 2>/dev/null || true
# Or reload via PID file:
# kill -HUP $(cat /run/myapp.pid) 2>/dev/null || true
endscript
}
Step 5 — Rotate by File Size Instead of Time
For high-volume logs that can grow very large within a single day, use size-based rotation:
/var/log/myapp/access.log {
# Rotate when file reaches 100 MB
size 100M
# Keep 10 rotated files
rotate 10
compress
delaycompress
missingok
notifempty
create 0640 appuser appgroup
postrotate
systemctl kill --kill-who=main --signal=HUP myapp 2>/dev/null || true
endscript
}
Note: size-based rotation only triggers when logrotate runs. If you need more frequent checks, run logrotate via a custom systemd timer (see Step 8).
Step 6 — Configure Retention by Days Instead of Count
The rotate N directive keeps N rotated files. With dateext and daily rotation, that equals N days. For explicit time-based deletion:
/var/log/myapp/*.log {
daily
rotate 90 # Keep 90 daily rotations = 90 days retention
compress
dateext
missingok
notifempty
create 0640 appuser appgroup
}
Step 7 — Test Without Actually Rotating
Always test a new logrotate configuration before relying on it:
# Dry run — show what would happen without actually rotating
logrotate -d /etc/logrotate.d/myapp
# Force rotation now (even if rotation is not due) — useful for testing
logrotate -f /etc/logrotate.d/myapp
# Verbose output to see what logrotate is doing
logrotate -v /etc/logrotate.conf
Step 8 — Run logrotate More Frequently with a Custom Timer
The default daily timer is sufficient for most logs. To run hourly for high-volume logs:
mkdir -p /etc/systemd/system/logrotate.timer.d
vi /etc/systemd/system/logrotate.timer.d/hourly.conf
[Timer]
OnCalendar=
OnCalendar=hourly
systemctl daemon-reload
systemctl restart logrotate.timer
Step 9 — View logrotate Status and History
logrotate maintains a state file at /var/lib/logrotate/logrotate.status:
# View the last rotation date for each log file
cat /var/lib/logrotate/logrotate.status
# View recent logrotate journal entries
journalctl -u logrotate --since today
Common directives reference
daily / weekly / monthly— rotation frequencysize SIZE— rotate when file exceeds SIZE (e.g., 100M, 1G)rotate N— keep N rotated archivescompress— compress archived files with gzipdelaycompress— compress the previous rotation, not the current onemissingok— no error if log file does not existnotifempty— skip rotation if file is emptycreate MODE USER GROUP— create new empty log file after rotationdateext— use date in the rotated filenamesharedscripts— run pre/postrotate once for all matching filescopytruncate— copy then truncate instead of rename; use for apps that cannot reopen log files
Troubleshooting
- Log file not rotating despite size exceeding threshold — logrotate runs only when triggered by its timer. If using size-based rotation, run
logrotate -f /etc/logrotate.d/myappmanually or increase the timer frequency. - Application writing to the old renamed log file after rotation — the app has the file handle open. Use
postrotateto send HUP to the process (to reopen log files), or usecopytruncateinstead of rename-based rotation. - Permission denied creating new log file — the
createdirective requires logrotate to run as a user that can create files in the log directory. Ensure the directory is writable by root or adjust permissions.
Conclusion
Your RHEL 9 server now has comprehensive log rotation configured: system logs are managed by the default logrotate rules, your custom application logs rotate daily with 30-day retention and proper compression, and the application is signalled to reopen its log file handles after rotation. The logrotate -d dry-run command lets you test any changes safely before they take effect.
Next steps: How to Use journalctl for Systemd Log Analysis on RHEL 9, How to Perform a System Security Audit with auditd on RHEL 9, and How to Monitor System Resources with htop and vmstat on RHEL 9.