Log files grow indefinitely unless something rotates and prunes them, which will eventually fill a disk and crash your services. On RHEL 8, logrotate is installed by default and handles rotation for most system logs out of the box via a systemd timer. For application logs your team manages, you need to write a custom drop-in configuration in /etc/logrotate.d/. This tutorial explains the global configuration, the key directives, writing and testing a custom rotation policy, and how to verify that the systemd timer is scheduled correctly.
Prerequisites
- A running RHEL 8 system with root or
sudoaccess - The
logrotatepackage (installed by default; confirm withrpm -q logrotate) - A log file or application whose output you want to manage
Step 1 — Exploring the Default Configuration
The global defaults live in /etc/logrotate.conf. Individual service drop-ins in /etc/logrotate.d/ inherit those defaults and can override any directive. Understanding the defaults prevents you from setting redundant options in every drop-in file.
# View the global configuration
cat /etc/logrotate.conf
# List all installed drop-in configurations
ls /etc/logrotate.d/
# View the drop-in for a system service as a reference
cat /etc/logrotate.d/syslog
Step 2 — Understanding Key logrotate Directives
Each drop-in file identifies one or more log paths followed by a block of directives. The directives below cover the most important options for production application logs. You can combine them freely; later directives in the same block override earlier ones.
# Directive reference (do not run — reference only)
#
# daily — rotate once per day
# weekly — rotate once per week
# rotate 14 — keep 14 rotated files before deleting
# compress — gzip old log files
# delaycompress — compress the previous rotation, not the current one
# (useful when the app still has the file open)
# missingok — do not error if the log file is missing
# notifempty — do not rotate if the log file is empty
# create 0640 appuser appgroup
# — create a fresh empty log after rotation with these perms
# sharedscripts — run postrotate/prerotate only once even with glob patterns
# postrotate — shell commands to run after rotation (e.g. reload app)
# endscript — marks the end of a postrotate/prerotate block
Step 3 — Writing a Custom Drop-in for an Application
Create a file in /etc/logrotate.d/ named after your application. The example below rotates /var/log/myapp/app.log daily, keeps 14 days of compressed history, and sends a SIGUSR1 to the application after rotation so it re-opens its log file handle without a full restart.
sudo tee /etc/logrotate.d/myapp </dev/null) 2>/dev/null || true
endscript
}
EOF
# Verify the file was written correctly
cat /etc/logrotate.d/myapp
Step 4 — Testing and Forcing a Rotation
Always test a new configuration with --debug before relying on it in production. The debug mode simulates a rotation without making any changes to disk. When you are confident the configuration is correct, use --force to trigger an immediate rotation regardless of whether rotation criteria are met — useful for verifying the postrotate script and confirming permissions.
# Dry run — shows what would happen without making changes
sudo logrotate --debug /etc/logrotate.d/myapp
# Force an immediate rotation (even if log was rotated today)
sudo logrotate --force /etc/logrotate.d/myapp
# Combine both for a verbose forced dry run
sudo logrotate --debug --force /etc/logrotate.d/myapp
# Check the logrotate state file to see last rotation timestamps
cat /var/lib/logrotate/logrotate.status | grep myapp
Step 5 — Verifying the systemd Timer
On RHEL 8, logrotate is driven by a systemd timer rather than a traditional cron job. The timer unit is logrotate.timer and it activates logrotate.service once per day. You can confirm the timer is active, check when it last ran, and manually trigger the service to simulate what would happen at the scheduled time.
# Confirm the timer is enabled and active
sudo systemctl status logrotate.timer
# List all timers and see next/last activation times
sudo systemctl list-timers logrotate.timer
# View the timer unit definition
sudo systemctl cat logrotate.timer
# Manually trigger logrotate (runs exactly as the timer would)
sudo systemctl start logrotate.service
# Check the service outcome
sudo systemctl status logrotate.service
sudo journalctl -u logrotate.service -b
Step 6 — Handling Edge Cases and Troubleshooting
A few common problems trip up new logrotate configurations. If a postrotate script fails, logrotate still completes the rotation but exits non-zero. Ensure your postrotate commands are idempotent and handle the case where the PID file does not yet exist. Compressed files that are still open by a process require delaycompress; omitting it can cause the application to write to a .gz file it cannot parse.
# Check for configuration syntax errors across all drop-ins
sudo logrotate --debug /etc/logrotate.conf 2>&1 | grep -i error
# Find log files that haven't been rotated as expected
sudo cat /var/lib/logrotate/logrotate.status
# Check disk usage before and after a forced rotation
du -sh /var/log/myapp/
sudo logrotate --force /etc/logrotate.d/myapp
du -sh /var/log/myapp/
# If an app holds a file descriptor open after rotation, use copytruncate
# instead of create (do NOT combine with delaycompress):
# copytruncate — copy log content to rotated file, then truncate original
Conclusion
You have explored the global logrotate configuration, learned the essential directives, written a custom drop-in for an application log, tested it with the debug and force flags, and confirmed that the systemd timer is running the rotation on schedule. With this knowledge you can keep log directories bounded in size while retaining a meaningful history for debugging and compliance purposes.
Next steps: How to Manage Systemd Services and Units on RHEL 8, How to Use tmux for Terminal Multiplexing on RHEL 8, and How to Configure Firewalld on RHEL 8.