KnowledgebaseLinux VPS › Logrotate recipes — keeping /var/log from eating your disk

Logrotate recipes — keeping /var/log from eating your disk

Every application that logs to a file under /var/log/ eventually fills the disk if nothing rotates it. Distro packages usually drop a logrotate config for their own logs; your custom application doesn't. This article covers the patterns for both writing new logrotate configs and fixing the common breakages in existing ones.

How logrotate works

Logrotate is a cron/timer-invoked utility that reads configs from /etc/logrotate.conf + /etc/logrotate.d/* and, for each log file matching a config, decides whether to rotate it based on the rules. "Rotating" means: rename the current log (foo.logfoo.log.1), optionally compress old ones, optionally delete ones past retention, and (usually) signal the writing process to reopen its file handle.

Logrotate doesn't run continuously. It runs daily via either /etc/cron.daily/logrotate or logrotate.timer, depending on distro. Between runs, your log can grow as much as it wants — sizing is a "this can't exceed X" cap, not a real-time bound.

The minimal custom config

Drop a file at /etc/logrotate.d/myapp:

/var/log/myapp/*.log {
    daily
    rotate 14
    missingok
    notifempty
    compress
    delaycompress
    copytruncate
}

What each directive does:

  • daily — rotate every day (alternatives: weekly, monthly, size 100M, hourly with hourly cron).
  • rotate 14 — keep 14 rotated copies. Older ones get deleted.
  • missingok — don't error if the log file doesn't exist (handy for apps that only sometimes write).
  • notifempty — skip rotation if the log is empty.
  • compress — gzip rotated copies. foo.log.2 becomes foo.log.2.gz.
  • delaycompress — leave the most recent rotation uncompressed for a day. Helps when an app holds the previous file handle briefly after reopen.
  • copytruncate — copy the log to its new name, then truncate the original. Works for apps that don't support reopen on signal.

The reopen-on-signal pattern (preferred when supported)

If your app responds to a signal by reopening its log file (most modern daemons do), copytruncate is suboptimal — it can lose log lines written during the copy. Use postrotate instead:

/var/log/nginx/*.log {
    daily
    rotate 14
    missingok
    notifempty
    compress
    delaycompress
    sharedscripts
    postrotate
        [ -f /var/run/nginx.pid ] && kill -USR1 $(cat /var/run/nginx.pid)
    endscript
}

The signal varies by app: nginx wants USR1, postfix wants HUP, your custom Python app may want SIGUSR2 if you wrote it that way. Check the app docs.

Size-based instead of date-based

For apps that log unpredictably (very quiet most days, occasional log floods), date-based rotation can leave you with 9 days of empty logs and one 50 GB monster. Size-based covers it:

/var/log/myapp/*.log {
    size 100M
    rotate 7
    compress
    delaycompress
    missingok
    notifempty
    copytruncate
}

Caveat: size rotations only happen when logrotate runs (daily by default). To enforce a real-time cap, set hourly rotation and move logrotate to a systemd hourly timer, or write to a journal/syslog daemon with built-in size limits instead.

The "I forgot to rotate and the disk is full" rescue

You can force a rotation immediately without waiting for cron:

# Dry-run first to see what would happen
logrotate -d /etc/logrotate.d/myapp

# Force a rotation
logrotate -f /etc/logrotate.d/myapp

# If the file is huge and you don't care about its contents
truncate -s 0 /var/log/myapp/huge.log    # zeroes the file in place, doesn't break the writing process

Don't rm a log file that an active process has open — the disk space won't free until the process closes its handle (which usually means a restart). The truncate -s 0 trick zeros the file content while leaving the inode alive, so the writing process keeps writing.

Common breakages

  • Logrotate says it rotated but the file kept growing. The app didn't reopen its handle — fix the postrotate script or switch to copytruncate.
  • compress hangs. The rotated file is huge; gzip takes minutes. Add delaycompress to defer compression by one day, or use compresscmd /usr/bin/zstd for faster compression.
  • Permissions errors after rotation. The new log file gets created with logrotate's umask (typically root's). Use create 0640 myapp myapp to set the expected ownership.
  • "file already exists" errors mid-rotation — a previous run died part way through and left half-rotated files. Manually clean up foo.log.1, foo.log.2 etc. and re-run.

What logrotate is NOT

It's not a log shipping or aggregation tool. If you need centralized logs across multiple boxes, send to a syslog server (rsyslog forwarding, or a hosted aggregator) and let that handle retention. See the related article on log shipping.

Also Read

« « Back

Powered by WHMCompleteSolution