journalctl Command: Tutorial & Examples

Every log line every daemon ever printed, indexed and filterable — no more digging through /var/log.

What It Is

journalctl is the front door to the systemd journal — a single, indexed, structured log that captures everything the kernel, the init system, and every service on the box emits. One command, one query language, every log on the system. If you've ever opened /var/log on a new server and felt the dread of which file?, this is the page that ends that feeling.

The promise sounds modest until you've lived without it. Before systemd shipped journald, every daemon on a Linux box picked its own log location, its own format, and its own rotation strategy — you'd have /var/log/syslog, /var/log/messages, /var/log/auth.log, /var/log/kern.log, /var/log/apache2/, /var/log/nginx/, and each one was a different text format that you grep'd with a different prayer. journalctl unified all of it into one queryable journal where every line carries metadata — which process, which user, which boot, which unit, what priority — and the queries compose. It's the most controversial systemd feature and its quietest win.

Your First Look

Type the command on any modern Linux box and you fall straight into a pager (less) at the oldest entry:

journalctl
-- Boot 9c2a8e3b80f7415cb89d4a3e2b1c5d6e --
May 20 09:14:02 web-01 kernel: Linux version 6.1.0-13-amd64 (debian-kernel@lists.debian.org)
May 20 09:14:02 web-01 kernel: Command line: BOOT_IMAGE=/boot/vmlinuz-6.1.0-13-amd64 root=UUID=...
May 20 09:14:03 web-01 systemd[1]: Starting systemd-journald.service - Journal Service...
May 20 09:14:03 web-01 systemd[1]: Started ssh.service - OpenBSD Secure Shell server.
May 20 09:14:03 web-01 sshd[612]: Server listening on 0.0.0.0 port 22.
May 27 14:21:07 web-01 sshd[18402]: Accepted publickey for holger from 10.0.0.5 port 51234 ssh2
...

Five columns per line, syslog-style: timestamp, hostname, process name and PID, then the message. The -- Boot ... -- markers separate reboots so you can see where the kernel handed off and the box came back up. Hit q to quit the pager (same key as less) — that one trips everyone the first time. Hit G to jump to the end, /pattern to search inside the pager.

The default view is the whole journal since journald started keeping records, which on a busy server can be gigabytes. Nobody actually reads it like this — every real session of journalctl starts by filtering. That's the next section, and it's the section that turns this tool from "log dump" into "structured query engine."

How I Read It

When something breaks, here is the exact sequence I run, in order, in maybe ten seconds.

First, scope to one unit and follow it live. Nine times out of ten I already know which service is misbehaving — ssh, nginx, postfix, mariadb — and the move is journalctl -u <service> -f. That -f follows in real time (the tail -f of the systemd world), scoped to one unit. Reload the service in another window and watch the log react. This single invocation is the workhorse.

Pro Tip

journalctl -u ssh.service -f is the most useful invocation in this entire page. It's the tail -f /var/log/... reflex you've built over the years, but unified and structured — no guessing which file sshd writes to, no missing the line because it went to a sibling file, no rotation surprises. Pair it with --since '5 min ago' to also get the recent context before the live stream starts. Veterans keep one tmux pane permanently on journalctl -u <theservice> -f while they deploy or test in another.

Second, ask the triage question: what's gone wrong since this boot? One command answers it:

journalctl -p err -b

-p err keeps only entries of priority err or worse (the syslog priorities — emerg, alert, crit, err, warning, notice, info, debug — numbered 0–7, lower is louder); -b restricts to the current boot. The output is short, almost always — the actual errors, with the noisy info chatter filtered out. This is my go-to triage move on any unfamiliar box.

Note

-b is the boot filter, and it's worth knowing all three forms. -b (or -b 0) means this boot; -b -1 means the previous boot; -b -2 two boots ago, and so on. journalctl --list-boots shows every boot the journal remembers with its ID and timestamps. When someone asks "why did the server reboot last night?", journalctl -b -1 -e (jump to the end of the previous boot) shows you the last lines the dying box wrote before it went down — invaluable, and you can't get this from rotated text logs.

Third, narrow by time when I'm hunting a specific incident: --since '1 hour ago', --since '2026-05-27 14:00' --until '2026-05-27 14:30'. The time parser accepts both absolute timestamps and human phrases ("yesterday", "10 min ago", "Monday"). Fourth, search inside messages with -g 'pattern' (PCRE) — the journal's own grep. And fifth, when I need everything about one line I pass -o verbose and see every field: the unit, the PID, the UID, the executable path, the cgroup, the boot ID, the source code file in the daemon that emitted it.

That's it: unit, priority, boot, time, pattern, verbose. Six dimensions; compose them freely.

The Filters Explained

The filters are the whole product. Every one of them composes with every other one — multiple filters are AND'd together — so journalctl -u ssh.service -p err -b --since '1 hour ago' reads as "errors from ssh since this boot, in the last hour." Here's the menu, with the reading for each.

  • -u <unit> — one systemd unit. journalctl -u ssh.service, -u nginx, -u mariadb. You can repeat -u to OR several units. --user-unit does the same for per-user units.
  • -p <priority> — by syslog severity. Single value (-p err) keeps that level and worse; a range (-p warning..emerg) is explicit. Levels: 0 emerg, 1 alert, 2 crit, 3 err, 4 warning, 5 notice, 6 info, 7 debug.
  • -b [ID] — by boot. -b for this boot, -b -1 for the previous, -b -N for N boots ago. --list-boots shows the menu.
  • -S / --since and -U / --until — by time. Accepts absolute (2026-05-27 14:00:00) or relative phrases (1 hour ago, yesterday, 08:00).
  • -g <pattern>PCRE regex against message text. --case-sensitive=yes to force, defaults to smart-case.
  • -t <ident> — by syslog identifier (the program name part — sshd, cron, kernel). -T excludes one.
  • -kkernel messages only. This is journald's version of dmesg, and unlike dmesg it persists across boots — journalctl -k -b -1 reads the previous boot's kernel ring buffer, which the live dmesg has long forgotten.
  • _FIELD=value matches — the structured query layer underneath all of the above. _SYSTEMD_UNIT=ssh.service, _PID=1234, _UID=0, _HOSTNAME=web-01, PRIORITY=3, _COMM=sshd. List every field journald indexes with journalctl -N; list every value of one field with -F _SYSTEMD_UNIT. Combine: journalctl _SYSTEMD_UNIT=ssh.service _PID=1234 PRIORITY=3 — that's the magic, and it's what -u, -p, -t etc. are sugar for.
  • -n <N> — last N lines. -r — reverse (newest first). -e — jump to the end in the pager (most common when you want recent). -f — follow (tail -f-style).

The output controls are worth knowing too: -o short-iso for sortable ISO timestamps in scripts, -o cat for just the messages (no timestamp/host/process — perfect for piping to grep or less), -o verbose to see all the indexed fields for each line, and -o json / -o json-pretty to export structured records you can pipe into jq or ship to an aggregator. The data has always been there; the format only hides it from you unless you ask.

Reading It by Example

The handful of invocations that will cover 95% of what you ever need.

Follow one service live, the way you used to tail -f a log file:

journalctl -u nginx.service -f

This replaces tail -f /var/log/nginx/error.log — except nginx's access log still goes to a file by default (see Gotchas), so use -f for errors and the file for access lines.

"What broke since this boot?" — the triage one-liner:

journalctl -p err -b

If the list is empty, this boot has had no errors. If it isn't, you have your starting point. Add -x (--catalog) to get human-readable explanations for messages that ship with a catalog entry — sometimes you get an actual paragraph about the error and a suggested fix.

Why did the box reboot? Read the previous boot's tail:

journalctl -b -1 -e

-e jumps to the end of the pager. The last hundred lines before -1 ended are almost always the story — a kernel panic, an OOM kill, a planned shutdown from systemctl. Pair with journalctl -k -b -1 to see only the kernel side.

A specific incident window:

journalctl --since '2026-05-27 14:00' --until '2026-05-27 14:30' -p warning

Warnings and worse, in a thirty-minute window. Add -u <service> to scope further.

Search across everything for a string:

journalctl -g 'failed password' --since yesterday

PCRE regex; this is the auth-failure scan that fits in one line and used to mean grepping /var/log/auth.log and its rotated .gz siblings.

One process, one PID, every field it logged:

journalctl _PID=18402 -o verbose

When ps gives you a PID and you want everything that process ever said. (PIDs are reused, so cross-check the timestamp.)

Pipe structured data to jq:

journalctl -u ssh.service --since '1 hour ago' -o json | jq -r 'select(.PRIORITY|tonumber <= 4) | .MESSAGE'

The data was always there.

Cheat Sheet

The invocations worth memorizing:

  • journalctl -u <unit> -f — follow one service live. The workhorse.
  • journalctl -p err -b — errors and worse, this boot. The triage move.
  • journalctl -b -1 -e — end of the previous boot. "Why did it reboot?"
  • journalctl -kkernel messages only (dmesg with history).
  • journalctl --since '1 hour ago' / -S ... -U ... — time window.
  • journalctl -g 'pattern' — regex search across messages.
  • journalctl -n 50 — last 50 lines. -r newest first. -e jump to end.
  • journalctl -t sshd — by syslog identifier (program name).
  • journalctl _PID=1234 / _SYSTEMD_UNIT=... / PRIORITY=3 — structured field matches; the real query layer.
  • journalctl -N — list every field name. -F <field> — list every value of one field.
  • journalctl --list-boots — every boot the journal remembers.
  • journalctl -o verbose — every field per entry. -o json-pretty — structured export.
  • journalctl -o cat — just the messages, no preamble. Pipes cleanly.
  • journalctl --disk-usage — how much disk the journal is eating.
  • journalctl --vacuum-time=7d / --vacuum-size=500M — prune old entries (run as root).
  • journalctl --verify — check journal file consistency.

How You'll Actually Use It

In practice journalctl lives in three moments. One: a service misbehaves and you do journalctl -u <service> -f in one pane, restart it in another, watch the startup messages, see the bind error or the config typo, fix it, watch it come up clean. Two: something happened overnight — journalctl -p warning -b --since 'yesterday 18:00' and you scroll the short list looking for the smoking gun. Three: the box rebooted unexpectedly and you do journalctl -b -1 -e to read the last words of the previous boot.

If you find yourself building a big grep pipeline against journalctl output, stop and check whether a structured filter exists — _SYSTEMD_UNIT=, PRIORITY=, _COMM=, -g, -t cover most "I want to grep for X" cases without the grep. For shipping logs to an aggregator, -o json is your friend. For a daemon that doesn't use journald at all (nginx access logs, apache per-vhost logs, mysql slow query logs that go to their own files), the old reflexes still work — tail -f, less, grep, cat on the file directly. journalctl is for everything systemd touches, which on a modern Linux box is almost everything — but not quite.

Gotchas

  • q to quit. The default pager is less and the keybindings are less's; new users sometimes Ctrl-C their way out and worry they broke something.
  • The default view is oldest first. Surprising if you expect newest. Use -e (jump to end) or -r (reverse) every time you want recent.
  • -f follows; without it, you get a snapshot. Same difference as tail vs tail -f.
  • Not every log is in the journal. nginx's access log, apache's per-vhost logs, mysql's slow query log, application logs that write directly to a file — those still live under /var/log and you read them with tail / less / grep / cat. What journalctl covers is stderr/stdout from every systemd unit plus the kernel and anything that talks to journald via the syslog socket — which is most daemons, but check before assuming.
  • The journal may not persist across reboots. Default storage on some distros is auto, which means RAM-only if /var/log/journal/ doesn't exist. To persist: sudo mkdir /var/log/journal && sudo systemctl restart systemd-journald. Check with journalctl --list-boots — if you only see this boot, your journal is volatile.
  • Disk usage can creep up. journalctl --disk-usage shows the total; --vacuum-time / --vacuum-size prune it. The default cap is 10% of the filesystem, configured in /etc/systemd/journald.conf.
  • -p err includes everything worse than err too. People sometimes expect "only err" — that's -p err..err.
  • -u nginx matches nginx.service automatically, but the journal also indexes the literal unit name — if you have nginx@foo.service and nginx@bar.service, the bare -u nginx won't match them. Use -u 'nginx@*' (glob) or be explicit.
  • Rotated journal files don't merge across boots automatically in every output mode. Almost always fine, but for forensics on archived journals, point at the directory with -D /var/log/journal/<machine-id>/.

History & Philosophy

The Linux logging world before systemd was syslog and its descendants — rsyslog being the standard for most of the 2000s and 2010s. The model was simple and unstructured: every program wrote a line of text to a Unix socket, rsyslog routed it to a file based on facility and priority, and logrotate periodically renamed and gzipped the files so the disk didn't fill. It worked, but every line was just a string — no metadata, no schema, no way to ask "which PID wrote this?" or "this is from which boot?" without parsing free text. Querying meant grepping a dozen files in a dozen formats.

When Lennart Poettering and Kay Sievers shipped journald with systemd in 2011, the bet was simple: make every log line a structured record with metadata attached automatically by the kernel and the init system, store them in an indexed binary journal, and give one query tool — journalctl — that filters on any field. The metadata is free because journald already knows it: a daemon talking to the syslog socket comes with a verified PID, UID, GID, executable path, cgroup, systemd unit, and boot ID, attached by the kernel. Programs don't need to opt in. The line Failed password for root becomes a record with _SYSTEMD_UNIT=ssh.service, _PID=18402, _UID=0, PRIORITY=4, _BOOT_ID=..., MESSAGE=Failed password for root, all queryable independently. That's the whole magic, and it's why the query language feels like a different decade — because it is.

It was, and to some extent still is, controversial — a binary log format in the Unix world that has always preferred plain text was a hard sell, and "I can't grep /var/log/messages any more" was a real complaint. The pragmatic answer: rsyslog and syslog-ng still work, often alongside journald (journald forwards to the syslog socket by default), so if you want flat text files too you have them. But the query power of the structured journal has quietly won the day, and a generation of admins now reach for journalctl reflexively the way the previous generation reached for tail and grep against /var/log/syslog.

See Also

Tired of remembering whether sshd logs go to auth.log, syslog, or journalctl -u ssh?

CleverUptime watches the symptoms this page is about — services failing or flapping, a box that rebooted without warning, OOMs the kernel quietly killed something for — and flags the underlying cause in plain language so you know which service to dig into before opening the journal.

Want to see your own server's health right now? One command, no signup, no install.

Check your server →