Skip to content

LogsExecutionUser activity

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

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

StoragePathNotes
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 filesystem.journalCurrently written file
Per-user filesuser-<UID>.journalDefault SplitMode=uid: separate files for regular (non-system) UIDs, still owned by the journal group, not the user
Archived filessystem@<id>-<seqnum>-<time>.journalRotated, 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, _CMDLINE and _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_IDENTIFIER and PRIORITY are supplied by the client, and any local user can write entries with logger or systemd-cat. Rely on the underscore fields for attribution.
  • It does not record commands typed in a shell unless something logs them.

Key fields

FieldMeaning
MESSAGEHuman-readable text (client supplied)
PRIORITYSyslog level 0 (emerg) to 7 (debug)
SYSLOG_IDENTIFIER, SYSLOG_FACILITYTag and facility as sent by the client
_PID, _UID, _GIDSender process, user and group (trusted)
_COMM, _EXE, _CMDLINEProcess name, executable path, command line (trusted)
_SYSTEMD_UNITUnit the sender belongs to
_BOOT_ID, _MACHINE_ID, _HOSTNAMEBoot, machine and host identity
_TRANSPORTHow it arrived: journal, syslog, stdout, kernel, audit, driver
_AUDIT_SESSION, _AUDIT_LOGINUIDAudit session and login UID of the sender
_SOURCE_REALTIME_TIMESTAMPEarliest trusted timestamp of the message at the source
__REALTIME_TIMESTAMP, __MONOTONIC_TIMESTAMPWhen journald received the entry
__CURSOROpaque 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 -D or --file every time. Without it, journalctl silently reads the analysis host's own journal.
  • -k implies the current boot; on offline evidence use _TRANSPORT=kernel -b all instead.
  • Many .journal~ files, --verify failures 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, --verify only detects corruption, not a clean rewrite.
  • Correlate _AUDIT_LOGINUID and _AUDIT_SESSION with auditd auid/ses values and with wtmp sessions.
  • A unit that logs only through stdout still gets _SYSTEMD_UNIT and _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.

See also