# logrotate: automatic log rotation and archiving

LLMS index: [llms.txt](/en/llms.txt)

---

Application logs fill up disk space within a week, and manually running `rm *.log` is a recipe for trouble. logrotate handles this automatically: it rotates, compresses, and deletes old files on a schedule. Let's see how it works and how to set it up in five minutes.

## How It Works

logrotate runs daily through cron. The default config lives in `/etc/logrotate.conf`, and additional configs are included from `/etc/logrotate.d/`. During rotation, the current file gets renamed, a new empty one is created, old copies are compressed and numbered.

The cycle looks like this:

```
app.log        →  app.log.1      (compressed: app.log.1.gz)
app.log.1.gz   →  app.log.2.gz
...
app.log.5.gz   →  deleted
```

The mechanism relies on rename or mv, so the process must hold the file descriptor open. If rotation isn't picked up by the application, you end up with duplication or an empty log.

## Configuration Structure

```bash
# /etc/logrotate.conf — global settings
weekly          # rotate weekly
rotate 4        # keep 4 copies
compress        # compress old logs
include /etc/logrotate.d/
```

Files from `/etc/logrotate.d/` override global values for specific logs. The format is straightforward:

```
/path/to/log {
    directive value
    ...
}
```

Directives inherit from the global config unless overridden. You can specify multiple paths separated by spaces or use a glob pattern — convenient for rotating all application logs in one block.

## Key Directives

Basic parameters covering 90% of use cases:

| Directive | Purpose | Example |
|-----------|---------|---------|
| `rotate N` | number of copies to keep | `rotate 7` |
| `size N` | size threshold to trigger rotation | `size 100M` |
| `missingok` | not an error if file is missing | — |
| `notifempty` | skip empty logs | — |
| `compress` | compress old logs (default gzip) | — |
| `dateext` | use date instead of number in name | — |
| `dateformat` | date format | `dateformat -%Y%m%d` |
| `postrotate ... endscript` | commands after rotation | reload service |
| `prerotate ... endscript` | commands before rotation | prepare directories |

`size` overrides `weekly`/`monthly`/`daily` when set. Rotation happens when the file reaches the specified size AND the interval has passed. So `size 100M` with `weekly` means rotation no earlier than a week and only if the file exceeds 100M.

> [!NOTE]
> `dateext` is incompatible with long filenames on some filesystems. If the log name plus date suffix exceeds 255 bytes — logrotate will fail.

## Example Config for an Application

Assume your `myapp` service writes to `/var/log/myapp/`. Config:

```bash
/var/log/myapp/*.log {
    daily
    rotate 14
    size 50M
    missingok
    notifempty
    compress
    dateext
    dateformat -%Y%m%d-%s
    sharedscripts
    postrotate
        systemctl reload myapp > /dev/null 2>&1 || true
    endscript
}
```

`sharedscripts` ensures `postrotate` runs once for all rotated files, not once per file. Without it, the script executes as many times as files match the rotation criteria.

For Python applications using standard library rotation:

```bash
/var/log/myapp/app.log {
    su root myapp
    daily
    rotate 7
    size 200M
    missingok
    notifempty
    compress
    postrotate
        /usr/bin/pkill -HUP -f "python.*myapp" || true
    endscript
}
```

`su` changes the rotation process owner — useful when the application runs under a separate user with restricted log permissions.

## Debugging and Manual Execution

Dry-run mode shows what would happen without making changes:

```bash
logrotate -d /etc/logrotate.d/myapp
```

Output contains each decision: which file gets renamed, which gets compressed, which commands run. Look for `renaming` and `running postrotate script` lines.

Force rotation bypassing the schedule:

```bash
logrotate -f /etc/logrotate.d/myapp
```

`-f` ignores the last rotation time and size conditions. Combining with `-d` is a safe way to verify before production:

```bash
logrotate -d -f /etc/logrotate.d/myapp
```

To rotate a specific log outside the schedule but respecting conditions — use the state file:

```bash
# check state
cat /var/lib/logrotate/status

# temporarily shift last rotation time
sed -i 's|/var/log/myapp/app.log.*|/var/log/myapp/app.log 2024-01-01-00:00:00|' /var/lib/logrotate/status
logrotate /etc/logrotate.d/myapp
```

Syntax check without execution:

```bash
logrotate -d /etc/logrotate.conf
```

If there are no errors — output is empty (without `-d`) or shows the action plan (with `-d`).

> [!WARNING]
> Do not edit `/var/lib/logrotate/status` manually in production without understanding the format. One mistake — and logrotate decides rotation already happened, skipping all files until the next cron run.

Default cron setup:

```bash
# /etc/cron.daily/logrotate
#!/bin/sh
test -x /usr/sbin/logrotate || exit 0
/usr/sbin/logrotate /etc/logrotate.conf
```

On most distros you don't need to touch this file. If you need rotation more often than once a day — add to `/etc/cron.hourly/` or write a separate cron job.
