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.