LFCS 132 🐧 Log Rotation with logrotate
The previous chapter covered the rsyslog configuration: the rules that write messages to text files under /var/log/. This chapter covers the mechanism that prevents those files from growing indefinitely. The logrotate command is a scheduled utility that rotates, compresses, removes, and mails log files. It is not a daemon; it is a process invoked by a systemd timer (or cron) once per day. The LFCS exam tests the logrotate.conf syntax, the per-log configuration files in /etc/logrotate.d/, and the manual invocation for testing. The critical point is that logrotate does not truncate a log file while a process is still writing to it. It renames the file (e.g., syslog to syslog.1) and creates a new file with the original name. The daemon must be told to reopen the new file, either by sending a SIGHUP signal or by using the copytruncate directive, which copies the file and truncates the original.
Key point: The global configuration is /etc/logrotate.conf. The per-log configurations are in /etc/logrotate.d/. The logrotate command is invoked by logrotate.timer (systemd) or cron.daily. The -d flag is a dry run. The -f flag forces a rotation even if the criteria are not met. The -v flag is verbose. The postrotate block is the mechanism for telling the daemon to reopen its log file.
Why log rotation requires explicit configuration
The disk full problem. A log file grows with every entry. A web server under load writes hundreds of megabytes per day. A crashed application in a restart loop writes gigabytes in a few hours. Without rotation, the filesystem fills, and the system fails to write anything, including the logs that would diagnose the failure. logrotate caps the file size, retains a limited number of rotated versions, and compresses the older ones.
The daemon file handle problem. A process writes to a file by holding an open file descriptor. When logrotate renames the file (syslog to syslog.1), the process still holds the old file descriptor, and it continues to write to the renamed file. The syslog file remains empty. The postrotate script sends a signal (SIGHUP) to the daemon, which closes the old file descriptor and opens a new one with the original name. The copytruncate directive is the alternative: it copies the file’s contents to the rotated version, then truncates the original to zero bytes. The daemon’s file descriptor remains valid, and the writes go to the empty file. The copytruncate is less efficient (it copies the data) and has a small window where a log entry can be lost, but it does not require the daemon to support SIGHUP.
The retention problem. A log file that is rotated every day and kept forever still consumes disk space. The rotate N directive keeps N rotated files (e.g., syslog.1, syslog.2, …, syslog.N). Older files are deleted. The maxage N directive deletes files older than N days, regardless of the rotate count. The compress directive compresses the rotated files with gzip, which reduces the disk usage significantly for text logs.
The permission problem. The rotated files inherit the permissions of the original file. If the original file is owned by root:adm with mode 640, the rotated file has the same ownership. The create directive specifies the mode, owner, and group for the new file created after rotation. The su directive runs the rotation as a specific user and group, which is required when the log file is in a directory that logrotate cannot access as root.
The trade-off. The rotation is a scheduled job. Between the rotation and the daemon reopening the file, there is a brief window where log entries may go to the old file or be lost. The copytruncate has a similar window. The postrotate with SIGHUP is the cleanest approach, but it requires the daemon to support the signal. The logrotate also supports a dateext option, which appends a date to the rotated file name (syslog-20261030) instead of a sequential number.
a. The global configuration and per-log files
The global configuration is /etc/logrotate.conf. It sets the defaults that apply to every log file unless overridden.
# /etc/logrotate.conf
weekly
rotate 4
create
dateext
compress
include /etc/logrotate.d
The weekly directive rotates once per week. The rotate 4 keeps four rotated files. The create directive creates a new file after rotation. The dateext appends the date to the rotated file name. The compress compresses the rotated files. The include /etc/logrotate.d loads the per-log configurations from the drop-in directory.
The per-log configuration files in /etc/logrotate.d/ override the global defaults for specific logs. Each file starts with the log file path, followed by a block of directives.
# /etc/logrotate.d/nginx
/var/log/nginx/*.log {
daily
rotate 14
compress
delaycompress
missingok
notifempty
create 0640 www-data adm
sharedscripts
postrotate
invoke-rc.d nginx rotate > /dev/null 2>&1
endscript
}
The daily overrides the global weekly. The rotate 14 keeps 14 files. The delaycompress postpones compression until the second rotation, which allows the daemon to finish writing to the file. The missingok prevents an error if the log file is missing. The notifempty skips rotation if the file is empty. The create sets the mode, owner, and group. The sharedscripts runs the postrotate once for the entire block, not once per file. The postrotate block sends the rotate command to Nginx.
b. Testing with logrotate -d and logrotate -f
The logrotate -d flag is a dry run. It shows what would be done without making changes. The -v flag is verbose. The -f flag forces rotation, ignoring the schedule.
# Dry run
sudo logrotate -d /etc/logrotate.conf
# Verbose dry run
sudo logrotate -dv /etc/logrotate.conf
# Force rotation
sudo logrotate -f /etc/logrotate.conf
# Test a specific config
sudo logrotate -d /etc/logrotate.d/nginx
The dry-run output shows the log file, the configuration that applies, the rotation decisions, and the commands that would run. It is the mandatory step before modifying a production configuration.
The logrotate -f forces the rotation regardless of the schedule. It is useful for testing the postrotate script and verifying that the daemon reopens the new file.
The logrotate state is stored in /var/lib/logrotate/status (or /var/lib/logrotate.status on older systems). The state file records the last rotation date for each log file. Deleting the state file resets the schedule.
# Check the state file
cat /var/lib/logrotate/status
c. The postrotate block and copytruncate
The postrotate block runs a script after the rotation. The endscript closes the block.
# /etc/logrotate.d/rsyslog
/var/log/syslog
/var/log/messages
{
rotate 7
daily
missingok
notifempty
delaycompress
compress
postrotate
/usr/lib/rsyslog/rsyslog-rotate
endscript
}
The /usr/lib/rsyslog/rsyslog-rotate script sends SIGHUP to the rsyslog daemon, which reopens its log files. Without this, rsyslog continues to write to the renamed file, and the new syslog file remains empty.
The copytruncate directive is the alternative. It copies the file’s contents to the rotated version, then truncates the original.
# /etc/logrotate.d/myapp
/var/log/myapp.log {
daily
rotate 7
compress
copytruncate
}
The copytruncate is used when the daemon does not support SIGHUP or when the administrator cannot find the correct signal. The copy is not atomic: a log entry written during the copy may be lost.
The postrotate and copytruncate are mutually exclusive approaches. The postrotate is the correct approach for daemons that support SIGHUP. The copytruncate is the fallback for daemons that do not.
Complete Example Session
# ============================================
# PART 1: VIEW GLOBAL CONFIG
# ============================================
cat /etc/logrotate.conf
# Shows the default directives.
# ============================================
# PART 2: VIEW PER-LOG CONFIG
# ============================================
cat /etc/logrotate.d/rsyslog
# Shows the rsyslog-specific configuration.
# ============================================
# PART 3: DRY RUN
# ============================================
sudo logrotate -d /etc/logrotate.conf
# Shows what would be rotated, without making changes.
# ============================================
# PART 4: FORCE ROTATION
# ============================================
sudo logrotate -f /etc/logrotate.conf
# Forces rotation now.
# ============================================
# PART 5: VERIFY ROTATED FILES
# ============================================
ls -la /var/log/*.log.*
# Shows the rotated files with .1, .2, .gz suffix.
# ============================================
# PART 6: ADD A CUSTOM LOG
# ============================================
sudo tee /etc/logrotate.d/myapp <<'EOF'
/var/log/myapp.log {
daily
rotate 7
compress
missingok
notifempty
create 0640 myapp myapp
postrotate
systemctl reload myapp.service > /dev/null 2>&1 || true
endscript
}
EOF
# ============================================
# PART 7: VALIDATE THE CONFIG
# ============================================
sudo logrotate -d /etc/logrotate.d/myapp
# Shows the rotation plan for myapp.log.
# ============================================
# PART 8: VERIFY THE STATE FILE
# ============================================
cat /var/lib/logrotate/status
# Shows the last rotation date for each log.
# ============================================
# PART 9: CHECK THE SYSTEMD TIMER
# ============================================
systemctl list-timers logrotate.timer
# Shows the next scheduled run.
The nine parts trace the log rotation: global config, per-log config, dry run, force rotation, verify rotated files, add a custom log, validate config, state file, and timer.
Quick Reference
Global Directives
| Directive | Purpose |
|---|---|
weekly | Rotate weekly |
rotate N | Keep N rotated files |
create | Create new file after rotation |
compress | Compress rotated files |
dateext | Date suffix instead of number |
include /etc/logrotate.d | Load drop-in configs |
Per-Log Directives
| Directive | Purpose |
|---|---|
daily | Rotate daily |
size 100M | Rotate when size exceeds |
missingok | Do not error if missing |
notifempty | Skip if empty |
delaycompress | Compress on second rotation |
sharedscripts | Run script once |
copytruncate | Copy then truncate |
postrotate | Run script after rotation |
endscript | End the script block |
Commands
| Command | Purpose |
|---|---|
logrotate -d <config> | Dry run |
logrotate -v <config> | Verbose |
logrotate -f <config> | Force rotation |
systemctl list-timers logrotate.timer | Next run |
cat /var/lib/logrotate/status | State file |
File Naming
| Pattern | Meaning |
|---|---|
syslog.1 | First rotated file |
syslog.2.gz | Second rotated file, compressed |
syslog-20261030 | Date-suffixed file (dateext) |
Best Practices
✅ Do This:
sudo logrotate -d /etc/logrotate.d/myapp
<!-- Dry run before deploying --> # ✅
postrotate
systemctl reload myapp.service
endscript
<!-- Tell daemon to reopen log file --> # ✅
missingok
notifempty
<!-- Skip missing and empty files --> # ✅
❌ Don’t Do This:
# Forget postrotate for daemons with open file handles
<!-- New log file remains empty --> # ❌
# Use copytruncate for large log files
<!-- Copy is slow and may lose entries --> # ❌
# Rotate without compression
<!-- Disk usage grows quickly --> # ❌
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
| Log file remains empty after rotation | Daemon holds file handle | postrotate with SIGHUP |
| Rotation errors on missing file | No missingok | Add missingok |
| Rotation does not happen | Timer not running | systemctl start logrotate.timer |
| Config not applied | Syntax error | logrotate -d shows the error |
| State file stale | Rotation date not updated | Delete state file and retry |
Real-World Examples
1. Basic Config
/var/log/myapp.log {
weekly
rotate 4
compress
}
2. Daily Rotation
/var/log/app/*.log {
daily
rotate 14
}
3. Force Rotation
sudo logrotate -f /etc/logrotate.d/nginx
4. Dry Run
sudo logrotate -d /etc/logrotate.d/nginx
5. Postrotate
postrotate
systemctl reload nginx
endscript
6. Copytruncate
/var/log/app.log {
copytruncate
rotate 7
}
7. Missing OK
missingok
notifempty
8. Custom Create
create 0640 www-data adm
9. Date Suffix
dateext
dateformat -%Y%m%d
10. Check Timer
systemctl list-timers logrotate.timer
Visual
Rotation Flow
┌──────────────────────────────────────────────┐
│ logrotate runs (daily timer) │
└──────────────────┬───────────────────────────┘
│
▼
┌──────────────────────────────────────────────┐
│ syslog → syslog.1 │
│ syslog.1 → syslog.2.gz │
│ syslog.2.gz → syslog.3.gz │
│ ... │
│ Oldest file deleted │
└──────────────────┬───────────────────────────┘
│
▼
┌──────────────────────────────────────────────┐
│ Create new syslog with mode 0640 │
└──────────────────┬───────────────────────────┘
│
▼
┌──────────────────────────────────────────────┐
│ postrotate: SIGHUP to rsyslog │
│ Daemon reopens syslog │
└──────────────────────────────────────────────┘
Postrotate vs Copytruncate
┌──────────────────────────────────────────────┐
│ postrotate (preferred) │
│ ├── rename file │
│ ├── create new file │
│ └── SIGHUP daemon │
└──────────────────┬───────────────────────────┘
│
▼
┌──────────────────────────────────────────────┐
│ copytruncate (fallback) │
│ ├── copy file to rotated version │
│ ├── truncate original to zero │
│ └── daemon keeps file handle │
└──────────────────────────────────────────────┘
Summary
| Item | Value |
|---|---|
| Global config | /etc/logrotate.conf |
| Drop-in directory | /etc/logrotate.d/ |
| Dry run | logrotate -d |
| Force | logrotate -f |
| Verbose | logrotate -v |
| Timer | logrotate.timer |
| State file | /var/lib/logrotate/status |
| Rotation signal | postrotate + SIGHUP |
| Fallback | copytruncate |
Key takeaways:
logrotateis a scheduled utility, not a daemon. It runs daily (or by size) and rotates the log files, compresses the older versions, and deletes the oldest based on therotatecount.- The daemon must reopen the log file. The
postrotateblock sends the signal that tells the daemon to close the old file descriptor and open the new one. Without this, the new file remains empty. copytruncateis the fallback. It copies the file and truncates the original, which preserves the daemon’s file descriptor. It is less efficient and may lose entries, but it does not require the daemon to supportSIGHUP.- The dry run is mandatory.
logrotate -dshows what would happen without changing anything. The configuration error is visible before the rotation runs. - The timer controls the schedule. The
logrotate.timer(systemd) or thecron.dailyscript invokes the utility. A stopped timer means the rotation never happens, and the log files grow indefinitely.
Remember: logrotate prevents log files from filling the disk. The rotation renames the file, creates a new one, and compresses the old. The postrotate script tells the daemon to reopen the new file. The copytruncate is the fallback. The rotate keeps N files. The compress saves space. The -d flag is the safe test. The timer is the schedule.
Stop using slow, ad-bloated tool sites! 🤮
🔎 Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)
⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free
🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!