Shore Up
A shelf of dated logbooks where each day's fat book is bundled, shrunk into a small sealed packet, and the oldest packets are lifted off the end of the shelf into a bin.
Linux

Rotate and Compress Application Logs Automatically with logrotate

Ketan Aagja7 min read
No ratings yet

Before you run this

This guide sets up logrotate to rotate and compress the log files of a custom application on a schedule, so a chatty app doesn't fill the disk. logrotate is already installed and running on Debian/Ubuntu; what you're adding is one small config file that tells it how to handle your app's logs. logrotate itself does the scheduling, compression, and pruning — you only describe the policy.

Privileges: you need root (via sudo) to write into /etc/logrotate.d/ and because logrotate runs as root when it fires. Editing the config and testing with sudo logrotate -d is safe; the destructive part is the real rotation.

Test first, and understand what it deletes. Read the config below and adjust it before you deploy it. logrotate's rotate N directive means it keeps N old copies and then permanently deletes anything older — that deletion is irreversible, so pick N deliberately. Always do a dry run (sudo logrotate -d, shown at the end) before forcing a real run, and try this on a test VM or with a throwaway log directory before pointing it at production logs. A wrong copytruncate/create choice can also cause your app to keep writing to a file descriptor that's been rotated away — I cover that below.

I'm assuming Debian 12 or Ubuntu 22.04 with the stock logrotate package. The directives are the same on RHEL/Alma; the difference is how logrotate is triggered (see the last section).

The scenario

Say you have an app that writes to /var/log/myapp/*.log and never rotates on its own. Replace myapp and that path with your real application throughout — those are placeholders.

The two things you must decide up front:

  1. How the app holds the file. Most daemons open their log file once and keep the handle. If logrotate renames the file out from under them, they'll keep writing to the now-renamed (or deleted) inode until they're told to reopen. There are two standard ways to deal with this, covered next.
  2. How long you keep logs, and whether to compress.

Choosing how the file gets rotated

There are two mainstream approaches. Pick one.

  • postrotate signal — logrotate renames the file, then runs a command to make the app reopen its log (typically a HUP signal or a service reload). This is the cleaner method if your app supports reopening its log file on a signal. Only use it if you know the app does — check its documentation.
  • copytruncate — logrotate copies the current log to the rotated name, then truncates the original file to zero length in place. The app never loses its file handle, so it needs no signal. The trade-off is a tiny window between copy and truncate where a few log lines can be lost, and it briefly uses extra disk. This is the safe default when you can't (or don't want to) signal the app.

I'll use copytruncate here because it works with virtually any application without special support. I'll note the postrotate variant afterward.

The config

Create /etc/logrotate.d/myapp (the filename should have no dots or extension — logrotate ignores files with certain extensions):

/var/log/myapp/*.log {
    daily                 # rotate once a day
    rotate 14             # keep 14 rotated files, then delete the oldest
    missingok             # don't error if the log is absent
    notifempty            # skip rotation if the file is empty
    compress              # gzip rotated files
    delaycompress         # compress on the NEXT cycle, not immediately
    copytruncate          # copy then truncate in place (no app restart needed)
    su myapp myapp        # run rotation as this user:group (see note)
}

A few notes on the non-obvious lines:

  • delaycompress leaves the most recent rotated file uncompressed for one cycle. With copytruncate this avoids compressing a file that might still be catching a stray write during the copy/truncate window. It's a sensible default; drop it if you'd rather compress immediately.
  • su myapp myapp tells logrotate which user and group to act as. This matters when /var/log/myapp/ is owned by the app's service account rather than root — recent logrotate versions warn or refuse to rotate a directory writable by a non-root user unless you specify su. Set both to the owner of the log directory. If the directory is owned by root, you can omit this line.

That's the whole policy. logrotate reads it automatically because /etc/logrotate.conf includes everything in /etc/logrotate.d/.

The postrotate variant

If your app reopens its log on HUP (or has a reload action), you'd replace copytruncate with a create-and-signal block instead. The shape is create (so a fresh file appears with the right permissions) plus a postrotate ... endscript block that signals the app. The exact signal or service command is app-specific, so check your application's documentation for how it reopens its log rather than guessing. If it's a systemd service with a working reload, a systemctl reload in the postrotate block is the usual pattern. Don't mix create with copytruncate — pick one model.

Dry-run it before trusting it

logrotate's debug mode parses your config and tells you exactly what it would do, without touching anything:

sudo logrotate -d /etc/logrotate.conf

Read the output. It will show your /var/log/myapp/*.log entry and say whether rotation is due and what files it would create. If you have a syntax error, it surfaces here. -d implies verbose and makes no changes — it's the safe way to check.

To actually force a rotation right now for testing (this does rotate):

sudo logrotate -vf /etc/logrotate.d/myapp

-f forces rotation even if the schedule says it isn't due yet; -v is verbose. Point it at just your file so you don't force-rotate every log on the box.

Verify it worked

After a forced run (or the next day's real run):

ls -l /var/log/myapp/

You should see the live myapp.log plus rotated copies. On the first cycle with delaycompress you'll see myapp.log.1 uncompressed; on later cycles you'll see myapp.log.2.gz, myapp.log.3.gz, and so on. Confirm the live file is still being written to by your app (tail -f), which proves copytruncate didn't break the handle.

logrotate records when it last rotated each file in its state file (on Debian/Ubuntu this is /var/lib/logrotate/status). If a scheduled rotation doesn't happen, check that file's timestamps and the verbose output; it tells you why logrotate decided a file was "not due."

Confirm it will run on schedule

logrotate doesn't schedule itself — something has to invoke it daily. On current Debian/Ubuntu that's a systemd timer:

systemctl list-timers logrotate.timer

If your system uses cron instead (older setups, and RHEL/Alma), the trigger is the script at /etc/cron.daily/logrotate. One or the other exists; check whichever your distro uses. You don't need to create the trigger — it ships with the package.

Undoing it

To stop rotating this app's logs, delete the config file:

sudo rm /etc/logrotate.d/myapp

That reverts the policy immediately — logrotate simply stops managing those files. What it cannot undo is work already done: files already compressed stay .gz, and any old logs already pruned by rotate N are gone for good. If you need those, restore them from backup. That's exactly why the dry run and a conservative rotate count matter before this ever touches production.

For the full directive reference — including size, maxsize, dateext, and olddir if you outgrow this basic policy — see the logrotate(8) man page (man logrotate), which is the authoritative source on your own system.

Written by
Ketan Aagja

Runs enterprise networks and security for a living, and writes Shore Up to turn two decades of hands-on Linux, Windows and mail-server work into guides you can actually use.

More about the author →

Was this article helpful?

Tap a star — no sign-in needed.

Be the first to rate this article.