systemd Journal: Linux Binary System Log
The systemd-journald binary log: structured entries with trusted process fields and microsecond UTC timestamps, often the only system log on modern Linux hosts.
- Location
- /var/log/journal/<machine-id>/
- Proves
- What services, processes, users and the kernel logged, with trusted PID/UID/executable and boot context
- Timestamps
- Microseconds since Unix epoch (UTC) for realtime; microseconds since boot for monotonic
- Access
- root (or systemd-journal, adm or wheel group); users can read their own user-UID journal
- Retention
- Size based: 10% of the file system capped at 4G by default; volatile copy lost at reboot
- Collection
- UAC, Velociraptor, cp -a, journalctl -o export
Tools
Compare all tools- Linux Log ParserIn browser
- journalctlCLI · built into Linux
- PlasoCLI · open source
- VelociraptorPlatform · open source
What it is
systemd-journald collects log data from the kernel, early boot, syslog calls, the native journal API, and the stdout/stderr of every systemd service, then stores it in indexed binary files. Each entry is a set of FIELD=value pairs. Fields whose name starts with an underscore are added by journald itself from the kernel's view of the sender (PID, UID, executable, unit, boot ID), so a process cannot forge them by writing a fake message.
On many current installs the journal is the primary or only system log. Debian 12 stopped installing rsyslog by default, and Fedora dropped it from default installs back in Fedora 20. If /var/log/auth.log or /var/log/secure is missing, check the journal before concluding that logs were wiped.
Where it lives
| Storage | Path | Notes |
|---|---|---|
| Persistent | /var/log/journal/<machine-id>/ | Survives reboot. <machine-id> matches /etc/machine-id |
| Volatile | /run/log/journal/<machine-id>/ | tmpfs, lost at shutdown. Only obtainable from a live system or memory |
| Active system file | system.journal | Currently written file |
| Per-user files | user-<UID>.journal | Default SplitMode=uid: separate files for regular (non-system) UIDs, still owned by the journal group, not the user |
| Archived files | system@<id>-<seqnum>-<time>.journal | Rotated, read-only |
| Dirty files | *.journal~ | Renamed after an unclean stop of journald or when corruption was detected |
| Configuration | /etc/systemd/journald.conf, /etc/systemd/journald.conf.d/*.conf, /usr/lib/systemd/journald.conf.d/ | Storage=, SystemMaxUse=, MaxRetentionSec=, ForwardToSyslog= |
Where the data lands depends on Storage=. volatile keeps it in /run, persistent writes to /var/log/journal, none discards it, and auto writes to /var only if /var/log/journal already exists. auto was the upstream default for years; systemd 259 changed the compiled-in default to persistent (distributions can override it at build time). Read the actual configuration on the image rather than assuming either.
The layout is the same across Debian/Ubuntu, RHEL/Fedora, SUSE and Arch. Distribution differences are in whether persistent storage is enabled and whether rsyslog also runs alongside.
What it proves
- Which unit or process emitted a message, with trusted
_PID,_UID,_COMM,_EXE,_CMDLINEand_SYSTEMD_UNIT. - Service starts, stops and failures (for example a malicious unit starting at boot, or rsyslog/auditd being stopped).
- sshd, sudo, su, PAM and cron messages, even when no text log exists.
- Kernel messages (
_TRANSPORT=kernel): USB attach, module loads, firewall LOG lines, OOM kills, segfaults. - Boot boundaries via
_BOOT_ID, which exposes unexpected reboots and clock jumps. - It does not prove a message is truthful:
MESSAGE,SYSLOG_IDENTIFIERandPRIORITYare supplied by the client, and any local user can write entries withloggerorsystemd-cat. Rely on the underscore fields for attribution. - It does not record commands typed in a shell unless something logs them.
Key fields
| Field | Meaning |
|---|---|
MESSAGE | Human-readable text (client supplied) |
PRIORITY | Syslog level 0 (emerg) to 7 (debug) |
SYSLOG_IDENTIFIER, SYSLOG_FACILITY | Tag and facility as sent by the client |
_PID, _UID, _GID | Sender process, user and group (trusted) |
_COMM, _EXE, _CMDLINE | Process name, executable path, command line (trusted) |
_SYSTEMD_UNIT | Unit the sender belongs to |
_BOOT_ID, _MACHINE_ID, _HOSTNAME | Boot, machine and host identity |
_TRANSPORT | How it arrived: journal, syslog, stdout, kernel, audit, driver |
_AUDIT_SESSION, _AUDIT_LOGINUID | Audit session and login UID of the sender |
_SOURCE_REALTIME_TIMESTAMP | Earliest trusted timestamp of the message at the source |
__REALTIME_TIMESTAMP, __MONOTONIC_TIMESTAMP | When journald received the entry |
__CURSOR | Opaque position of the entry, useful to cite an exact record |
The on-disk file starts with the signature LPKSHHRH. Header fields include machine_id, tail_entry_boot_id, head_entry_realtime and tail_entry_realtime, and data objects may be compressed with XZ, LZ4 or ZSTD.
Timestamps
All journal times are stored in microseconds. __REALTIME_TIMESTAMP counts from the Unix epoch in UTC; __MONOTONIC_TIMESTAMP counts from boot and is only meaningful together with _BOOT_ID. journalctl displays times in the analysis host's local zone unless you pass --utc.
# 1789874047512345 usec -> UTC
date -u -d @$((1789874047512345 / 1000000))
journalctl -D "$J" --utc -o short-iso-precise --since "2026-09-20 03:00" --until "2026-09-20 04:00"
Realtime values follow the system clock, so a manipulated clock produces misleading wall times. Compare the monotonic order within a boot and look for systemd-timesyncd or NTP adjustment messages.
Retention
Retention is size based by default. SystemMaxUse= defaults to 10% of the file system, capped at 4G; SystemKeepFree= defaults to 15%, also capped at 4G; individual files rotate at one eighth of SystemMaxUse= (max 128M) or after MaxFileSec= (one month). MaxRetentionSec= is off by default. On a busy server the journal may only span days, while a quiet VM can hold years. Deletion routes include journalctl --vacuum-time/--vacuum-size/--vacuum-files, --rotate followed by vacuuming, Storage=volatile or none, or simply removing files as root.
Collection
Copy the whole directory, including .journal~ files, with ownership and timestamps preserved. On a live host, also grab /run/log/journal before shutdown because it does not survive a reboot.
# Live, as root
journalctl --flush # push /run data to /var if persistent
tar -C / -cpf /media/ir/journal.tar var/log/journal run/log/journal etc/systemd/journald.conf etc/systemd/journald.conf.d
journalctl -o export > /media/ir/journal.export # lossless serialisation
# Dead box (read-only mount)
cp -a /mnt/evidence/var/log/journal /cases/2026-017/
UAC collects *.journal and *.journal~ under /var/log (the volatile /run/log/journal is covered only by its separate run_log artifact, included in ir_triage and full) and runs journalctl --list-boots; Velociraptor's Linux.Forensics.Journal artifact parses the files directly on the endpoint.
Parsing
journalctl on an analysis host is the reference reader. Use a recent version: files written with newer incompatible features (such as compact mode) are refused by older readers.
J=/mnt/evidence/var/log/journal
journalctl -D "$J" --list-boots --utc
journalctl -D "$J" --header | less # file ranges, boot IDs, state
journalctl -D "$J" -b all _TRANSPORT=kernel --utc # kernel messages from every boot
journalctl -D "$J" SYSLOG_IDENTIFIER=sshd SYSLOG_IDENTIFIER=sshd-session -o json > sshd.json
journalctl -D "$J" -F _SYSTEMD_UNIT # every unit that ever logged
journalctl --file "$J/<machine-id>/system@*.journal~" --utc
journalctl -D "$J" --verify
Plaso's systemd_journal parser puts entries into a super timeline, and Velociraptor's parse_journald() exposes them in VQL. 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
- Pass
-Dor--fileevery time. Without it, journalctl silently reads the analysis host's own journal. -kimplies the current boot; on offline evidence use_TRANSPORT=kernel -b allinstead.- Many
.journal~files,--verifyfailures clustered around the incident, or a boot list that ends abruptly without a shutdown sequence are leads for tampering or a hard power-off. Without Forward Secure Sealing,--verifyonly detects corruption, not a clean rewrite. - Correlate
_AUDIT_LOGINUIDand_AUDIT_SESSIONwith auditdauid/sesvalues and with wtmp sessions. - A unit that logs only through stdout still gets
_SYSTEMD_UNITand_EXE, which makes the journal the best place to see what a suspicious systemd unit actually printed. - If rsyslog also runs, compare both: a line present in the journal but missing from auth.log/secure points to text log editing.