wtmp, btmp, utmp and lastlog: Linux Login Records
Binary Linux login records: wtmp session history, btmp failed logins, utmp live sessions, lastlog per-UID last login, and their wtmpdb/lastlog2 successors.
- Location
- /var/log/wtmp
- Proves
- Who logged in, on which terminal, from which host, when the session ended, and when the system rebooted
- Timestamps
- utmp records: Unix epoch seconds + microseconds (32-bit fields), UTC. wtmpdb: microseconds since epoch
- Access
- Readable by all for wtmp/utmp/lastlog; root (or utmp group) for btmp
- Retention
- logrotate: monthly, 1 old generation for wtmp and btmp; lastlog until overwritten
- Collection
- UAC, Velociraptor, cp -a
Tools
Compare all tools- Linux Log ParserIn browser
- last / lastbCLI · built into Linux
- utmpdumpCLI · built into Linux
- wtmpdbCLI · open source
- PlasoCLI · open source
- VelociraptorPlatform · open source
What it is
Linux keeps login accounting in a family of binary files that share one record layout, struct utmp (see wtmp and btmp). wtmp is an append-only history of logins, logouts, boots and shutdowns; btmp records failed logins; utmp holds only the sessions open right now. lastlog is a different format: one fixed-size slot per UID with that account's most recent login.
These files are written by login, sshd, display managers and PAM modules, independently of syslog. That independence is their forensic value: they often survive when text logs are edited, and they disagree with them when only one source was tampered with.
Because the classic record uses 32-bit time fields, recent distributions replace them with SQLite databases: wtmpdb for wtmp and lastlog2 (part of util-linux since 2.40) for lastlog.
Where it lives
| File | Path | Reader | Notes |
|---|---|---|---|
| wtmp | /var/log/wtmp, /var/log/wtmp.1 | last | History of sessions, reboots, shutdowns |
| btmp | /var/log/btmp, /var/log/btmp.1 | lastb | Failed logins, mode 0600/0660 |
| utmp | /run/utmp (/var/run/utmp) | who, w | tmpfs: current sessions only, gone after reboot |
| lastlog | /var/log/lastlog | lastlog | Sparse file indexed by UID |
| wtmpdb (openSUSE, upstream default) | /var/lib/wtmpdb/wtmp.db | wtmpdb last | SQLite, Y2038 safe |
| wtmpdb (Debian 13) | /var/log/wtmp.db | wtmpdb last | Debian keeps a link at /var/lib/wtmpdb/wtmp.db |
| lastlog2 | /var/lib/lastlog/lastlog2.db | lastlog2 | SQLite, Y2038 safe |
Distribution notes: openSUSE Tumbleweed and MicroOS dropped /run/utmp, /var/log/wtmp and /var/log/lastlog in favour of systemd-logind, wtmpdb and lastlog2. Debian 13 (trixie) removed last, lastb and lastlog; the replacements wtmpdb and lastlog2 (with their PAM modules) must be installed separately after an upgrade (lslogins still ships with util-linux), so a trixie host may have no login database at all. RHEL/Fedora and Ubuntu LTS releases still commonly use the classic files; check what exists on the image.
What it proves
- Interactive logins with user, terminal (
pts/N,tty1,:0), remote host or IP, start and end time. - Reboots and shutdowns (
rebootandshutdownpseudo-users on terminal~), including abrupt ends where a session has no logout (crashorgone - no logoutinlast). - Failed logins with the attempted user name, which may include passwords typed into the user field by mistake. Handle btmp as sensitive.
- The last login of each account from
lastlog, even after wtmp rotated away. - It does not show non-interactive activity:
ssh host command, scp/sftp and many automated logins do not allocate a tty and are often absent. Use auth.log/secure and the journal for those.
Key fields
struct utmp (384 bytes per record on common 64-bit glibc systems):
| Field | Meaning |
|---|---|
ut_type | 1 RUN_LVL, 2 BOOT_TIME, 5 INIT_PROCESS, 6 LOGIN_PROCESS, 7 USER_PROCESS (login), 8 DEAD_PROCESS (logout) |
ut_pid | PID of the login process |
ut_line | Terminal (pts/0), max 32 bytes |
ut_id | Terminal suffix or inittab ID |
ut_user | User name, max 32 bytes; empty on logout records in wtmp |
ut_host | Remote host, or kernel version for boot records, max 256 bytes |
ut_session | Session ID |
ut_tv | tv_sec and tv_usec, both 32-bit on biarch platforms |
ut_addr_v6 | Remote IP (IPv4 in the first word) |
struct lastlog: ll_time (32-bit epoch), ll_line (32 bytes), ll_host (256 bytes), 292 bytes per slot at offset UID x 292. wtmpdb stores Type, User, Login, Logout, TTY, RemoteHost and Service (the PAM service) per row.
Timestamps
Classic records store Unix epoch seconds plus microseconds, UTC. last prints in the analysis host's local zone, so set TZ=UTC. The 32-bit fields overflow in January 2038. wtmpdb stores Login and Logout as microseconds since the epoch.
TZ=UTC last -F -i -x -f /mnt/evidence/var/log/wtmp
sqlite3 wtmp.db "SELECT User, TTY, RemoteHost, Service,
datetime(Login/1000000,'unixepoch'), datetime(Logout/1000000,'unixepoch') FROM wtmp;"
Retention
logrotate's standard stanzas rotate wtmp and btmp monthly and keep one old generation (wtmp.1, btmp.1), so expect one to two months of history. The stanzas live in /etc/logrotate.conf or /etc/logrotate.d/wtmp and btmp depending on distribution. utmp is cleared at every boot. lastlog keeps one record per UID forever, overwritten at each login. wtmpdb rotate moves old entries into dated wtmp_<date>.db files next to the database (/var/lib/wtmpdb/ upstream, /var/log/ on Debian).
Collection
Copy the files with metadata. Size matters: a wtmp that is not a multiple of the record size, or much smaller than expected, is a finding.
cp -a /mnt/evidence/var/log/{wtmp,wtmp.1,btmp,btmp.1,lastlog} /cases/2026-017/ 2>/dev/null
cp -a /mnt/evidence/var/lib/wtmpdb /mnt/evidence/var/lib/lastlog /cases/2026-017/ 2>/dev/null
cp -a /mnt/evidence/var/log/wtmp.db /cases/2026-017/ 2>/dev/null
# Live only: current sessions
cp -a /run/utmp /media/ir/ && who -a > /media/ir/who.txt
UAC collects /var/log and /var/run/utmp and runs last, lastb, lastlog and utmpdump in live response. Velociraptor's Linux.Sys.LastUserLogin parses wtmp* files on the endpoint.
Parsing
last -F -i -x -f wtmp # full dates, IPs, system events
lastb -F -i -f btmp # failed logins
utmpdump wtmp > wtmp.txt # every record and field, text
wtmpdb last -F -f wtmp.db # wtmpdb databases
lastlog2 -d lastlog2.db # lastlog2 databases
The classic lastlog command reads the live system's file, so for an image copy read the 292-byte slots with a short script (slot number = UID, skip all-zero slots). Plaso's utmp parser handles wtmp, btmp and utmp for super timelines. Linux Log Parser reads auth.log / secure, syslog, journal files, audit.log and wtmp / btmp / lastlog in the browser and merges them into one timeline with rebuilt login sessions; nothing is uploaded.
Investigator tips
- Run on an analysis host whose
lastmatches the record layout (64-bit glibc). Debian 13 no longer shipslast; use a util-linux build from another distribution, or Plaso. - In
utmpdumpoutput, look for zeroed records, timestamps that go backwards, and logins with no logout.utmpdump -rconverts edited text back to binary, so tampering is easy for root: prove it through disagreement with auth.log, the journal and audit.logUSER_LOGIN. - A burst of btmp entries from one IP followed by a wtmp login from the same IP is the classic brute-force success pattern.
- A
lastlogtime newer than the last matching wtmp record means wtmp lost entries (rotation or deletion). - Compare the
ut_hostkernel version onrebootrecords with the installed kernels to spot boots into an unexpected kernel. - Zero-byte wtmp or btmp with an old mtime on an active server is a common anti-forensics sign (
> /var/log/wtmp).