Skip to content

logrotate for Custom Daemons

Log rotation is missing for your daemon, the log file has grown to dozens of gigabytes, the disk is full, and monitoring is screaming. systemd-journald and syslog-ng rotate on their own, but if your custom daemon writes directly to a file, rotation falls to logrotate. Here is how to configure it for a specific service.

Why write a custom logrotate config

Packages from the repository usually drop their config into /etc/logrotate.d/, but for self-built daemons or those compiled from source, there is none. Without a config the file grows without limits. logrotate runs via a systemd timer (logrotate.timer) or cron and reads all files from /etc/logrotate.d/. Creating a single file is enough to start the rotation cycle.

Note

Verify that the logrotate package is installed and the timer is active: systemctl status logrotate.timer. On most distributions it is enabled by default.

copytruncate vs create — when to use which

These are two fundamentally different approaches to renaming and creating a new file.

copytruncatecreate
MechanismCopies the current file, truncates the original in placeRenames the old file, creates a new one with correct permissions
Application impactNo restart neededDaemon must be able to open the new file (typically via SIGHUP)
Risk of lost linesYes — entries written between copy and truncate can fall into the gapMinimal — renaming is atomic
When to useDaemon cannot re-open its file (e.g., written in Go without signal handling)Daemon supports SIGHUP or systemd-notify
Warning

copytruncate is a compromise. Lines written between cp and truncate are lost. For high-traffic services this can mean hundreds of lost lines per second.

If the daemon can receive signals, use create and restart it via postrotate.

delaycompress and how it affects the archive chain

By default logrotate compresses the rotated file in the same cycle. The problem: if the daemon is still writing to the old file (or has not yet re-opened the new one), compression breaks everything.

delaycompress defers compression by one cycle. The chain looks like this:

app.log          ← current
app.log.1        ← rotated, not yet compressed
app.log.2.gz     ← compressed, two cycles ago
app.log.3.gz     ← compressed, three cycles ago

Without delaycompress the transition is harsher: app.log immediately becomes app.log.1.gz, and if the daemon is still writing to app.log through its descriptor, data goes into the compressed archive — or is lost entirely.

Tip

delaycompress only makes sense together with create and compress. With copytruncate it works but loses its meaning — the file is truncated in place, so compression can happen immediately.

Integration with systemd notify

If the daemon supports sd_notify(3), you do not need to rely on postrotate with a manual restart. systemd can restart the service based on a signal from logrotate.

In the logrotate config, specify:

postrotate
    systemctl kill -s HUP my-daemon.service
endscript

Or, if the daemon listens on NOTIFY_SOCKET:

postrotate
    systemctl notify-reload my-daemon.service
endscript
Note

systemctl notify-reload is available starting from systemd 231. It sends RELOADING=1 followed by READY=1 — the standard mechanism for notifying a configuration reload.

For copytruncate, postrotate is usually unnecessary — truncating the file in place does not require daemon involvement.

Example working config

Suppose daemon my-app writes to /var/log/my-app/app.log and supports SIGHUP and sd_notify.

/var/log/my-app/app.log {
    daily
    rotate 14
    compress
    delaycompress
    missingok
    notifempty
    create 0640 myapp myapp
    postrotate
        systemctl notify-reload my-app.service >/dev/null 2>&1 || true
    endscript
}

Flags:

FlagWhat it does
dailyRotate every day
rotate 14Keep 14 archives
compressgzip compression
delaycompressDelay compression by one cycle
missingokDo not error if the file is absent
notifemptyDo not rotate an empty file
create 0640 myapp myappCreate new file with correct permissions and owner
postrotateNotify systemd of reload

To test without actually rotating:

logrotate -d /etc/logrotate.d/my-app

The -d flag runs in debug mode — it shows what would be done, without modifying any files.

Warning

After creating the config, the first rotation happens on the next timer tick. To force an immediate check: logrotate -f /etc/logrotate.d/my-app. This rotates the file right away, so use it carefully on production.