journalctl: The Commands You’ll Actually Use

Before systemd, finding out why something broke meant knowing which file to open. Auth failures in one place, kernel messages in another, whatever your application felt like writing somewhere else entirely. The journal replaced that with one queryable store, which is genuinely better, right up until you need something out of it and can’t remember the flag.

This is the set of journalctl commands worth committing to memory, in the order you’d actually reach for them. Everything below was run on Ubuntu 26.04 with systemd 259 and Oracle Linux 9.6 with systemd 252, and the differences between the two matter more than you’d expect.

Start with three

journalctl -u nginx        # one service
journalctl -f              # follow live
journalctl -b              # this boot only

Those three answer most questions. The rest of this narrows them down.

One thing to set straight early: by default journalctl shows the oldest entries first and drops you in a pager. That surprises people coming from tail. Add -e to jump to the end, or -r to reverse the whole thing so newest is on top.

Logs for one service

-u takes a unit name, and this is where the two families diverge immediately:

journalctl -u ssh -n 4 --no-pager
Dec 20 13:22:12 mylinux1 sshd-session[3514]: pam_unix(sshd:session): session opened for user asif(uid=1000) by asif(uid=0)
Dec 20 13:22:13 mylinux1 sshd-session[3514]: pam_unix(sshd:session): session closed for user asif
Dec 20 13:22:57 mylinux1 sshd-session[3560]: Accepted password for asif from 192.168.100.22 port 49162 ssh2
Dec 20 13:22:57 mylinux1 sshd-session[3560]: pam_unix(sshd:session): session opened for user asif(uid=1000) by asif(uid=0)

Same command on Oracle Linux needs a different unit name, and the process logging it is named differently too:

sudo journalctl -u sshd -n 4 --no-pager
Dec 20 13:22:13 app1 sshd[6261]: pam_unix(sshd:session): session opened for user asif(uid=1000) by asif(uid=0)
Dec 20 13:22:13 app1 sshd[6261]: pam_unix(sshd:session): session closed for user asif
Dec 20 13:22:45 app1 sshd[6457]: Accepted password for asif from 192.168.100.22 port 49161 ssh2
Dec 20 13:22:46 app1 sshd[6457]: pam_unix(sshd:session): session opened for user asif(uid=1000) by asif(uid=0)

ssh on Debian and Ubuntu, sshd on the Red Hat side. And notice sshd-session against plain sshd in the process column. OpenSSH 9.8 split the listener from the per-connection process, so on anything recent the thing writing your auth logs is sshd-session. That bites when you filter by process name rather than unit.

If you can’t remember what a unit is called, ask the journal which ones have logged anything:

journalctl -F _SYSTEMD_UNIT

-n limits the number of lines, defaulting to 10 if you give it no number. --no-pager dumps straight to the terminal, which is what you want when piping into something else. More on units themselves in understanding systemd service management.

Watching it happen

journalctl -u nginx -f

This is the tail -f equivalent and it’s the single most useful thing here. Leave it running in one window, reproduce the problem in another, and watch the error appear as you cause it. Beats guessing at timestamps afterwards.

Combine it with a unit or you’ll be reading everything the machine does, which on a busy box scrolls faster than you can read.

Narrowing by time

journalctl --since "10 min ago"
journalctl --since yesterday --until today
journalctl --since "2025-12-20 13:00" --until "2025-12-20 14:00"
journalctl -u nginx --since "1 hour ago"

The time parser is generous. It takes yesterday, today, now, relative offsets like -2h, and full timestamps. Quote anything with a space in it, because otherwise your shell splits it and journalctl sees two arguments it doesn’t understand.

When someone reports “it broke around three”, this is the flag that turns that into something you can read.

Only the bad news

-p filters by priority, and it’s inclusive of everything more severe:

sudo journalctl -p err -n 3 --no-pager
Dec 20 13:08:17 app1 alsactl[813]: alsa-lib main.c:1554:(snd_use_case_mgr_open) error: failed to import hw:0 use case configuration -2
Dec 20 13:08:21 app1 kernel: overlayfs: idmapped layers are currently not supported
Dec 20 13:08:23 app1 firewalld[845]: ERROR: NAME_CONFLICT: new_policy_object(): 'docker-forwarding'

The levels run from 0 to 7: emerg, alert, crit, err, warning, notice, info, debug. So -p err gives you err, crit, alert and emerg, and hides the noise below.

journalctl -p err -b is a good first command on a machine that’s misbehaving. Everything that called itself an error since boot, nothing else.

This boot, or the one before

journalctl -b        # current boot
journalctl -b -1     # the boot before this one
journalctl --list-boots

-b -1 is the one you want after an unexplained reboot, because it shows what the machine was saying on its way down.

Assuming it kept the record. Which brings us to the thing most guides skip.

Your logs might not survive a reboot

Here is the same command on two machines. Ubuntu 26.04 first:

journalctl --list-boots
 -2 aed60fc0d4ee40c2acbada3c9e93c1fc Fri 2025-11-14 04:33:06 PKT Fri 2025-11-14 06:55:03 PKT
 -1 948fe74179bd40d1a9b8eb7978e190dc Sat 2025-12-20 03:09:47 PKT Sat 2025-12-20 04:10:19 PKT
  0 f43eb25996f141bd9ef945a39b0b4391 Sat 2025-12-20 13:08:13 PKT Sat 2025-12-20 13:22:12 PKT

Three boots of history. Now Oracle Linux 9.6, a stock install:

journalctl --list-boots
IDX BOOT ID                          FIRST ENTRY                 LAST ENTRY
  0 f656c66bd60b420abccb20c09ecaac56 Sat 2025-12-20 13:08:07 PKT Sat 2025-12-20 13:22:13 PKT

One. And asking for the previous boot says so plainly:

journalctl -b -1
Specifying boot ID or boot offset has no effect, no persistent journal was found.

The reason is a single directory. journald ships with Storage=auto, which means it writes to disk only if /var/log/journal already exists, and otherwise keeps everything in /run, which is memory. On reboot, memory goes away.

ls -d /var/log/journal          # Ubuntu
/var/log/journal

ls -d /var/log/journal          # Oracle Linux
ls: cannot access '/var/log/journal': No such file or directory

Think about what that means the next time a server reboots unexpectedly. You log in, run journalctl -b -1 to find out why, and the evidence was thrown away by the reboot you’re investigating.

Creating the directory is the whole fix:

sudo mkdir -p /var/log/journal
sudo systemd-tmpfiles --create --prefix /var/log/journal
sudo systemctl restart systemd-journald

Or set it explicitly in /etc/systemd/journald.conf so it doesn’t depend on a directory existing:

Storage=persistent

Do this on any machine you’d have to explain an outage on. It costs some disk and it’s the difference between having logs and having a shrug.

Fields, which is what makes the journal worth it

Journal entries aren’t lines of text, they’re structured records. Every entry carries metadata you can filter on directly, and this is the part that genuinely beats grepping a flat file.

journalctl -u ssh -n 1 -o json-pretty
	"PRIORITY" : "6",
	"_COMM" : "sshd-session",
	"_HOSTNAME" : "mylinux1",
	"SYSLOG_IDENTIFIER" : "sshd-session",
	"_PID" : "3653",
	"MESSAGE" : "pam_unix(sshd:session): session opened for user asif(uid=1000) by asif(uid=0)",
	"_SYSTEMD_UNIT" : "ssh.service",

Any of those becomes a filter:

journalctl _COMM=sshd-session -n 2 --no-pager
Dec 20 13:23:38 mylinux1 sshd-session[3653]: Accepted password for asif from 192.168.100.22 port 64760 ssh2
Dec 20 13:23:38 mylinux1 sshd-session[3653]: pam_unix(sshd:session): session opened for user asif(uid=1000) by asif(uid=0)
journalctl _PID=3653
journalctl _UID=1000
journalctl _TRANSPORT=kernel

Two rules govern how these combine, and they’re not obvious. Repeat the same field and you get OR. Use different fields and you get AND. So _PID=100 _PID=200 means either process, while _UID=1000 _COMM=bash means that user running that command.

Use -F to see what values a field actually holds before guessing:

journalctl -F _COMM
journalctl -F _SYSTEMD_UNIT

Searching the text

journalctl -u ssh --grep "Accepted" -n 2 --no-pager
Dec 20 13:23:38 mylinux1 sshd-session[3653]: Accepted password for asif from 192.168.100.22 port 64760 ssh2
Dec 20 13:23:28 mylinux1 sshd-session[3609]: Accepted password for asif from 192.168.100.22 port 64747 ssh2

--grep takes a regular expression and is case insensitive as long as your pattern is all lowercase. Put a capital letter in it and the match turns case sensitive, which is a small surprise the first time.

You can pipe into grep instead, and sometimes that’s easier, but --grep searches the MESSAGE field only. That means it won’t match on a hostname or a unit name that happens to contain your word, which is usually what you wanted.

Changing the shape of the output

sudo journalctl -u sshd -n 3 -o cat --no-pager
pam_unix(sshd:session): session closed for user asif
Accepted password for asif from 192.168.100.22 port 49161 ssh2
pam_unix(sshd:session): session opened for user asif(uid=1000) by asif(uid=0)

-o cat strips the timestamp, hostname and process, leaving the message. Ideal when you’re feeding output into another tool and the metadata is just noise.

The others worth knowing: short-iso for sortable timestamps, json for one record per line if something downstream is parsing it, and verbose for every field on an entry when you’re trying to work out what you can filter on.

Kernel messages

journalctl -k
journalctl -k -b -1

-k is shorthand for _TRANSPORT=kernel and covers what dmesg shows, with one significant advantage: it goes back further than the kernel ring buffer, which overwrites itself. Assuming the journal is persistent, which we covered.

How much disk this is using

journalctl --disk-usage
Archived and active journals take up 54.3M in the file system.

Turning on persistence means it grows, so know how to trim it:

sudo journalctl --vacuum-size=500M
sudo journalctl --vacuum-time=30d
sudo journalctl --vacuum-files=5

Those delete archived journals until the limit is met. To stop it getting there in the first place, cap it in /etc/systemd/journald.conf:

SystemMaxUse=1G

Left alone, journald keeps up to 10% of the filesystem and stops at 15% free space remaining. Fine on a big disk, less fine on a 20GB VM where 10% disappears quietly. Worth setting deliberately rather than discovering it with df.

When it doesn’t do what you expect

What you see What it usually means
Nothing for a unit you know exists Wrong unit name. ssh and sshd differ by distro.
“no persistent journal was found” /var/log/journal missing. Logs are in memory only.
Fewer entries than expected as a normal user Try sudo. Access depends on group membership.
Output is old and you wanted recent Default order is oldest first. Use -e or -r.
Stuck in a pager q quits. --no-pager avoids it.
--since ignored or an error Unquoted argument with a space in it.
Journal is huge --vacuum-size, then set SystemMaxUse.
Filtering by _COMM=sshd finds nothing OpenSSH 9.8 and later log as sshd-session.

What to remember

Realistically you’ll use four of these daily. -u to pick a service, -f to watch it, --since to bracket a time, and -p err to skip to the problems. Everything else you can look up.

The one thing worth doing right now rather than looking up later is checking whether /var/log/journal exists on your servers. If it doesn’t, you’re one reboot away from having no evidence at all, and you’ll find that out at the worst possible moment.

If scheduled jobs are what you’re chasing in the logs, cron jobs in Linux covers where those land, and Linux commands and directory structure covers the wider shell if any of this assumed too much.

Asif Khan

Asif Khan

DevOps, Linux, and Cloud engineer with 10+ years building infrastructure that stays up, and automation so I do not have to fix it at 2am. My focus is automation, security, and systems built to survive failure, using Infrastructure as Code, CI/CD, containers, monitoring, and cloud platforms instead of manual fixes. This blog is where I write down what worked, and just as often what didn't the first time around. Real tutorials you can run yourself, troubleshooting notes pulled from actual outages, and the kind of detail you only get once you've hit the problem firsthand. If you're trying to get Linux, DevOps, and cloud infrastructure to behave, that's what you'll find here: reproducible solutions you can understand, test, and apply in your own environment.