| |

LFCS 132 🐧 Log Rotation with logrotate

⏱️ Estimated read: 11 min | |

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

DirectivePurpose
weeklyRotate weekly
rotate NKeep N rotated files
createCreate new file after rotation
compressCompress rotated files
dateextDate suffix instead of number
include /etc/logrotate.dLoad drop-in configs

Per-Log Directives

DirectivePurpose
dailyRotate daily
size 100MRotate when size exceeds
missingokDo not error if missing
notifemptySkip if empty
delaycompressCompress on second rotation
sharedscriptsRun script once
copytruncateCopy then truncate
postrotateRun script after rotation
endscriptEnd the script block

Commands

CommandPurpose
logrotate -d <config>Dry run
logrotate -v <config>Verbose
logrotate -f <config>Force rotation
systemctl list-timers logrotate.timerNext run
cat /var/lib/logrotate/statusState file

File Naming

PatternMeaning
syslog.1First rotated file
syslog.2.gzSecond rotated file, compressed
syslog-20261030Date-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

PitfallWhy It HappensFix
Log file remains empty after rotationDaemon holds file handlepostrotate with SIGHUP
Rotation errors on missing fileNo missingokAdd missingok
Rotation does not happenTimer not runningsystemctl start logrotate.timer
Config not appliedSyntax errorlogrotate -d shows the error
State file staleRotation date not updatedDelete 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

ItemValue
Global config/etc/logrotate.conf
Drop-in directory/etc/logrotate.d/
Dry runlogrotate -d
Forcelogrotate -f
Verboselogrotate -v
Timerlogrotate.timer
State file/var/lib/logrotate/status
Rotation signalpostrotate + SIGHUP
Fallbackcopytruncate

Key takeaways:

  • logrotate is 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 the rotate count.
  • The daemon must reopen the log file. The postrotate block 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.
  • copytruncate is 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 support SIGHUP.
  • The dry run is mandatory. logrotate -d shows 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 the cron.daily script 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.

Was this helpful?


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!