Skip to content

A permanent logrotate guard for btmp and wtmp

Adityo Guni Waluyo

Internet-facing servers pile up failed-login logs with no guard. One logrotate drop-in, the su directive, and a logrotate -d gate close the problem for good.

TL;DR

btmp logs failed SSH logins and can grow past hundreds of megabytes between weekly rotations. A drop-in logrotate rule using maxsize 50M rotates oversized files early, while rotate 2 and compress keep archives small. Validate with logrotate -d before deploying; add su root utmp or logrotate refuses on group-writable directories.

Why btmp and wtmp can bloat unnoticed

One morning my collector dashboard showed disk usage creeping up on the mail server. du -sh /var/log confirmed it: a btmp.1 archive at 108M plus an active btmp at 47M. I cleaned it up by hand back then — I already told that story in a previous article. The problem: cleaning alone means the same event returns a few weeks later. What was missing is a permanent guard: a dedicated logrotate rule installed once, validated before it goes live, then left alone.

btmp is the file holding every failed login attempt, normally read through lastb [3]. wtmp is its counterpart: all logins and logouts, maintained by login, init, and getty [2]. Both are binary, not plain text, so their contents stay invisible until you open them with the right command. last -f /var/log/btmp.1 even works for peeking into an old archive.

A server facing the internet gets probed for logins every second, and each failure appends a line to btmp. The standard weekly rotation does not contain this growth between schedules: the file can blow past hundreds of megabytes before rotation day arrives. The maxsize directive closes that gap; when the file exceeds the limit, logrotate rotates it early instead of waiting for the interval [1]. Unlike size, which ignores the schedule entirely, maxsize still respects the interval but loosens its upper bound [1].

Inside the guard at /etc/logrotate.d/btmp-wtmp

I installed a single drop-in file rather than touching the stock /etc/logrotate.conf:

/var/log/btmp /var/log/wtmp {
    weekly
    maxsize 50M
    rotate 2
    compress
    create
    su root utmp
}

weekly plus maxsize 50M is the key pair: a normal weekly schedule, but oversized files rotate early. rotate 2 and compress keep archives from piling up: only two remain, and they get squeezed. create with no arguments makes the replacement file inherit the mode, owner, and group of the original [1]. That matters because sshd and login still need to write to the file after rotation, and those permissions must stay tight.

The su root utmp line is the part most often missed. On that server /var/log is group-writable, and logrotate refuses to rotate files in such a directory under its default identity; the man page recommends su for directories whose control is not exclusively root's [1]. The official logrotate issue tracker carries the exact error message: "parent directory has insecure permissions" [4]. The wording sounds alarming, but the meaning is plain: tell logrotate which identity to rotate as. The choice is not arbitrary: the issue's maintainer answer shows su following the parent directory's identity [4].

logrotate -d as the gate before installation

Before this file goes live, my playbook validates it with logrotate -d. Debug mode is pure simulation: no files are touched, and the state file is not updated either [1]. It is cheap, which makes it fit for automation.

The first run got caught: the gate rejected the rule because the su directive was missing, with the same parent-directory error as above. Added su root utmp, ran it again, passed with zero errors. A second run on the same host: changed=0, the guard is idempotent.

Without that gate, a broken config surfaces when the rotation schedule fires and fails in production. With the gate, only the playbook run fails, at a time I chose myself. To be blunt: automation that writes config without a dry-run gate is deploying on hope. logrotate -d costs one line in the playbook, and it caught my directive mistake before it ever reached production.

One closing note on priorities. Not every log needs this treatment, because journald already guards many of them with its vacuum cap. But btmp and wtmp are different: they sit outside journald, written directly by sshd and login, and their lines grow fastest exactly when the server is under attack. That is the moment the file clogs up first. A guard this cheap belongs on every host with SSH exposed to the internet.

Sources

[1] logrotate(8), Linux man-pages

[2] utmp(5), Linux man-pages

[3] last(1), util-linux

[4] logrotate issue #589: Insecure permissions in parent directory

Related articles