static.apur.in

journalctl recipes I kept re-deriving

2026-08-30

Time and boot

journalctl -u nginx --since "2026-08-14" --until "2026-08-15"
journalctl -b -1 -u sshd              # the previous boot, e.g. before a crash
journalctl -k -b -0 --since "-30min"  # kernel ring, this boot
journalctl --list-boots               # what boots are even still stored

-b -1 is the single most useful flag and the one I forget most often. Note that boots disappear according to retention, not according to when you needed them — see the retention section.

Filtering on fields instead of grepping

Every entry is a set of fields, so equality tests are exact and much faster than | grep:

journalctl SYSLOG_IDENTIFIER=sshd
journalctl _PID=1421
journalctl _COMM=nginx -p err
journalctl -u postgresql -u postgresql@14-main       # several units: union
journalctl -u docker --since today -p 3..4           # errors and warnings only

Several values for the same field are joined with +, which keeps the match exact rather than textual. -p takes a level or a range, which is how you stop drowning in debug output from one chatty service.

Output that is readable or machine-readable

journalctl -u app -o cat              # message only, good for pasting
journalctl -u app -o json | jq -r 'select(.PRIORITY=="3") | .MESSAGE'
journalctl -g "authentication failure" --case-sensitive=0   # needs PCRE build
journalctl -k | grep -Ei "i/o error|oom"

-o cat is underrated: it removes timestamps and unit names, which is what you want when pasting a log into a ticket and are about to be asked "what does this actually say".

Polling without re-reading everything

For a script that runs on a timer and must report only what is new, use the cursor rather than timestamps — timestamps collide at the same second and you will miss or repeat entries:

journalctl -u app --cursor-file=/var/lib/mymon/cursor --since today -p err -o cat

The file is updated after a successful read. For one-shot use, --show-cursor prints --cursor=s=…;i=…;b=…, which you can feed back with --after-cursor=.

Retention, and the surprise that follows

journalctl --disk-usage
journalctl --vacuum-size=200M
journalctl --vacuum-time=3weeks

Default Storage=auto keeps the journal in /run/log/journal until /var/log/journal exists, and on a fresh minimal install that can mean volatile storage: the journal is empty after a reboot, exactly when you want it. If you care about one incident, it will be this one. Fix it before you need it:

# /etc/systemd/journald.conf.d/persistent.conf
[Journal]
Storage=persistent
SystemMaxUse=500M
# then: systemctl restart systemd-journald && journalctl --flush

Another machine, and damaged files

scp -r otherhost:/var/log/journal /tmp/j/
journalctl -D /tmp/j -u sshd --since "3 days ago"
journalctl --verify | tail -5          # structural check of the stored files
journalctl -m                          # merge local + journald-forwarded remote entries

Reading a copied journal works because the format is self-contained: no database, no host keys, no client. It is the nicest property of the whole system and the reason log collection is a solved problem here and nowhere else.

← manuals