Knowledgebase › Linux 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.log → foo.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.

Related Articles

Ready to deploy? Spin up a VPS at LYLIX — instant provisioning, full root, self-service portal.
Deploy a Linux VPS →

← All Linux VPS articles