Skip to content
DevOps devops logging 6 min read

Log Rotation with logrotate

Logs are useful, but they never stop growing. A busy web app can write hundreds of megabytes a day, and a single log file that is never trimmed will quietly eat your entire disk. When the disk hits 100%, your app cannot write files, your database may refuse new connections, and the server can become impossible to log into. Log rotation is the standard cure: it renames the current log, starts a fresh empty one, compresses the old ones, and deletes anything past a retention limit. On Ubuntu, the tool that does this is logrotate.

What log rotation is

Think of log rotation like a stack of notebooks. When the current notebook (your active log) gets full or old, you set it aside, label it with a date, and grab a brand-new blank one. You keep the last few old notebooks in a drawer, and you throw away anything older than that. Log rotation does exactly this, automatically, on a schedule.

A typical rotation turns one file into a numbered, compressed history:

app.log          <- the live file your app writes to right now
app.log.1        <- yesterday's log
app.log.2.gz     <- two days ago, compressed
app.log.3.gz     <- three days ago, compressed

The full-disk incident. In the System Administration section we walked through a server that went down because /var/log/myapp/app.log had grown to 40 GB and filled the root partition. The fix in the moment was to truncate the file, but the real fix is rotation: a 10-line logrotate config would have kept that file under control forever. Disk-full outages caused by unrotated logs are one of the most common — and most preventable — incidents in operations.

How logrotate runs on Ubuntu

logrotate is a small program that reads config files, decides which logs are due for rotation, and acts on them. It does not run continuously. Instead it is triggered once a day by a systemd timer (a scheduled job managed by systemd, the program that starts and supervises services on Ubuntu). On a fresh Ubuntu 22.04 or 24.04 server, logrotate is already installed. You can confirm it and see the timer:

# Confirm logrotate is installed
logrotate --version

# See the timer that fires logrotate daily
systemctl list-timers logrotate.timer

Output:

logrotate 3.21.0

NEXT                        LEFT       LAST                        UNIT             ACTIVATES
Tue 2026-06-16 00:00:00 UTC 8h left    Mon 2026-06-15 00:00:00 UTC logrotate.timer  logrotate.service

The main config file is /etc/logrotate.conf, which holds global defaults. Individual programs drop their own rules into the /etc/logrotate.d/ directory — one small file per app. This is where you put your own configs. Anything in /etc/logrotate.d/ is automatically included, so you never edit the system’s files; you just add your own.

When to use logrotate (and when not to)

Use logrotate for any program that writes to its own plain-text file under /var/log/ — your app, Nginx, Apache, PostgreSQL, and so on. It is the right tool whenever you control a growing file on disk. Do not use logrotate for logs that go to the systemd journal (journalctl): the journal manages its own size and retention through journald, configured in /etc/systemd/journald.conf with settings like SystemMaxUse=500M. Pointing logrotate at journal files would fight that system. In short: file on disk → logrotate; journal → journald.

Writing a config for an app log

Suppose your app writes to /var/log/myapp/app.log and /var/log/myapp/error.log. Create a dedicated config file for it:

sudo nano /etc/logrotate.d/myapp

Put this inside:

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

Every line earns its place. Here is what each directive does:

DirectiveWhat it doesWhy it matters
dailyRotate once per daySets the rotation frequency (alternatives: weekly, monthly, or size 100M)
rotate 14Keep 14 old files, then deleteYour retention window — here, two weeks of history
compressGzip rotated filesOld logs shrink ~90%, saving disk
delaycompressWait one cycle before compressingKeeps the most recent rotated file readable for quick tail
missingokDon’t error if the file is absentAvoids noise when an app hasn’t logged yet
notifemptySkip rotation if the log is emptyNo pointless empty .gz files
create 0640 myapp myappRecreate the fresh log with these permissions/ownerEnsures the app can keep writing
sharedscriptsRun postrotate once, not per fileThe wildcard matches two files, but reload once
postrotate ... endscriptShell commands to run after rotationTells the app to reopen its log files

Why postrotate matters

When logrotate renames app.log to app.log.1, your app may still be holding the old file open and happily writing to the renamed file — which means the new app.log stays empty. The fix is the postrotate block: it runs a command after rotation to nudge the app into reopening its files. For a systemd service that supports it, systemctl reload myapp is the clean way. Nginx and Apache do this automatically via their own configs (/etc/logrotate.d/nginx, /etc/logrotate.d/apache2), which already ship with the right postrotate lines.

Gotcha: if your app does not reopen log files on reload, use copytruncate instead of create + postrotate. It copies the log, then truncates the original in place, so the app’s open file handle keeps working. The trade-off is a tiny window where log lines written during the copy can be lost.

Testing your config safely

Never wait until midnight to find out your config is broken. logrotate has a dry-run mode that prints exactly what it would do without touching anything:

# Check syntax and show the plan, change nothing
sudo logrotate --debug /etc/logrotate.d/myapp

Output:

reading config file /etc/logrotate.d/myapp
Handling 1 logs

rotating pattern: /var/log/myapp/*.log  after 1 days (14 rotations)
considering log /var/log/myapp/app.log
  log needs rotating
rotating log /var/log/myapp/app.log, log->rotateCount is 14
renaming /var/log/myapp/app.log to /var/log/myapp/app.log.1
running postrotate script

Once the dry run looks right, you can force a real rotation immediately to prove it end to end (the -f flag means “force, even if not yet due”):

sudo logrotate -f /etc/logrotate.d/myapp
ls -lh /var/log/myapp/

Output:

total 8.0K
-rw-r----- 1 myapp myapp    0 Jun 16 09:14 app.log
-rw-r----- 1 myapp myapp 1.2K Jun 16 09:14 app.log.1

logrotate also keeps a small state file at /var/lib/logrotate/status recording when each log was last rotated, so it never rotates twice in one day.

Rotating by size instead of time

Time-based rotation (daily) is fine for steady traffic, but a sudden flood of errors can fill a disk between daily runs. For high-volume or bursty logs, rotate by size, or combine both:

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

Here the log rotates whenever it crosses 100 MB, regardless of the clock, keeping the last 7 chunks. Because the daily timer is what checks the size, pair size-based rules with a more frequent check (or an hourly cron entry) if your logs can grow that fast within a day.

Best Practices

  • One config file per app in /etc/logrotate.d/ — never edit the bundled OS files; add your own.
  • Always --debug first, then -f to test, before trusting a new config in production.
  • Set retention deliberately with rotate N — match it to how long you actually need to investigate incidents, not “forever”.
  • Compress old logs to reclaim ~90% of the space, and use delaycompress so the newest rotated file stays instantly readable.
  • Handle the open-file problem: use postrotate + reload for apps that reopen files, or copytruncate for those that don’t.
  • Don’t rotate journald logs with logrotate — cap them in /etc/systemd/journald.conf instead.
  • Monitor disk usage anyway; rotation is your safety net, not a reason to stop watching /var/log with alerts.
Last updated June 15, 2026
Was this helpful?