Without a log rotation strategy, application log files will grow unbounded until they fill your disk and bring services down. Logrotate is the standard RHEL utility that automates log file rotation, compression, and cleanup on a scheduled basis with no daemon process required. It runs daily via a cron job and processes configuration rules you define per application, making it easy to manage logs for dozens of services from a central location. This tutorial explains how logrotate works, walks through writing a custom rotation config for an application, covers the most useful directives, and shows how to set up logrotate for Nginx, Apache, and PostgreSQL.
Prerequisites
- RHEL 9 server with root or sudo access
- Logrotate installed (included by default; verify with
rpm -q logrotate) - Basic familiarity with systemd service management
Step 1 — Understand How Logrotate Works
Logrotate is triggered once per day by a cron job installed at /etc/cron.daily/logrotate. It reads the global defaults from /etc/logrotate.conf and then processes every file in /etc/logrotate.d/. Global defaults apply to all configs unless overridden in an individual config file.
# View the global configuration
cat /etc/logrotate.conf
# List per-application configs installed by packages
ls /etc/logrotate.d/
# View the cron job that triggers logrotate daily
cat /etc/cron.daily/logrotate
# Check the logrotate state file (tracks last rotation date per log file)
cat /var/lib/logrotate/logrotate.status | head -20
Step 2 — Write a Custom Logrotate Config
Create a logrotate configuration file for a custom application whose logs live in /var/log/myapp/. Place it in /etc/logrotate.d/ so it is automatically picked up each day.
sudo tee /etc/logrotate.d/myapp > /dev/null < /dev/null 2>&1 || true
endscript
}
EOF
# Verify the syntax is valid
sudo logrotate --debug /etc/logrotate.d/myapp
Step 3 — Understand the Key Directives
The following directives cover the most common logrotate use cases. Combining them gives you precise control over when files rotate and how long they are kept.
# FREQUENCY
# daily — rotate once per day
# weekly — rotate once per week
# monthly — rotate once per month
# size 100M — rotate when file exceeds 100 MB (overrides time-based triggers)
# RETENTION
# rotate 14 — keep 14 rotated files before deleting the oldest
# maxage 30 — delete rotated files older than 30 days (works with rotate)
# COMPRESSION
# compress — compress rotated files with gzip
# delaycompress — delay compression by one cycle (lets running apps finish writing)
# compresscmd /usr/bin/xz — use xz instead of gzip
# compressext .xz
# FILE CREATION
# create 0640 user group — create a new empty log file after rotation with given perms
# nocreate — do not create a new file (useful if the app creates its own)
# SAFETY
# missingok — do not error if the log file is missing
# notifempty — skip rotation if the log file is empty
# dateext — append date to rotated filename instead of a number
# SCRIPTS
# sharedscripts — run postrotate/prerotate once for all matched files, not per file
# postrotate / endscript — shell commands to run after rotation (e.g., reload the app)
# prerotate / endscript — shell commands to run before rotation
Step 4 — Test Your Configuration
Use the --debug and --force flags to validate and test a logrotate config without waiting for the daily cron job to run. Debug mode performs a dry run and prints exactly what logrotate would do.
# Dry-run: show what would happen without making changes
sudo logrotate --debug /etc/logrotate.d/myapp
# Force rotation now (ignores the 'lastrun' timestamp in the state file)
sudo logrotate --force /etc/logrotate.d/myapp
# Force rotation with verbose debug output
sudo logrotate --debug --force /etc/logrotate.d/myapp
# Check that rotated file was created
ls -lh /var/log/myapp/
# Verify the state file was updated
grep myapp /var/lib/logrotate/logrotate.status
Step 5 — Configure Logrotate for Nginx, Apache, and PostgreSQL
Many packages install logrotate configs automatically, but it is useful to review and customize them. The following examples show the key settings for three common services.
# --- Nginx ---
sudo tee /etc/logrotate.d/nginx > /dev/null < /dev/null 2>&1 || true
endscript
}
EOF
# --- Apache (httpd) ---
sudo tee /etc/logrotate.d/httpd > /dev/null < /dev/null 2>&1 || true
endscript
}
EOF
# --- PostgreSQL ---
sudo tee /etc/logrotate.d/postgresql > /dev/null <<'EOF'
/var/lib/pgsql/data/log/*.log {
daily
rotate 14
compress
delaycompress
missingok
notifempty
dateext
copytruncate
}
EOF
Note the use of copytruncate for PostgreSQL: instead of moving the log file and creating a new one (which requires the application to reopen the file descriptor), logrotate copies the contents and then truncates the original to zero bytes in place. This is necessary for processes that do not support log file reload signals.
Conclusion
You now have a solid understanding of how logrotate works on RHEL 9 and have written custom rotation configs for your own application as well as Nginx, Apache, and PostgreSQL. The combination of daily, rotate 14, compress, and delaycompress is a sensible default for most application logs; adjust rotate and maxage to match your disk capacity and compliance requirements. Always validate new configs with logrotate --debug before relying on them in production, and periodically review /var/lib/logrotate/logrotate.status to confirm rotations are completing on schedule.
Next steps: How to Install Prometheus and Grafana on RHEL 9, How to Set Up the ELK Stack on RHEL 9, and How to Install Netdata on RHEL 9.