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.
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.
copytruncate | create | |
|---|---|---|
| Mechanism | Copies the current file, truncates the original in place | Renames the old file, creates a new one with correct permissions |
| Application impact | No restart needed | Daemon must be able to open the new file (typically via SIGHUP) |
| Risk of lost lines | Yes — entries written between copy and truncate can fall into the gap | Minimal — renaming is atomic |
| When to use | Daemon cannot re-open its file (e.g., written in Go without signal handling) | Daemon supports SIGHUP or systemd-notify |
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:
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.
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:
Or, if the daemon listens on NOTIFY_SOCKET:
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.
Flags:
| Flag | What it does |
|---|---|
daily | Rotate every day |
rotate 14 | Keep 14 archives |
compress | gzip compression |
delaycompress | Delay compression by one cycle |
missingok | Do not error if the file is absent |
notifempty | Do not rotate an empty file |
create 0640 myapp myapp | Create new file with correct permissions and owner |
postrotate | Notify systemd of reload |
To test without actually rotating:
The -d flag runs in debug mode — it shows what would be done, without modifying any files.
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.