journalctl is the command to read the system logs on any distribution that
uses systemd. It replaces reading the files in /var/log/ by hand with a
structured journal that you filter by service, by priority, by date and by
boot. It is the main diagnosis tool of the Linux administrator.
What you will learn
- Read the system logs and the logs of a specific service
- Filter the logs by date, priority and boot
- Follow the logs in real time with
-f - Configure the retention and the maximum size of the journal
- Manage the permissions to read the logs
- Use the advanced output formats
In which context?
journalctl comes into play in every diagnosis situation, from a failed service to a disk filling up:
- A service refuses to start, the logs explain the error
- The system rebooted unexpectedly, read the previous boots
- An application produces intermittent errors, filter by priority
- The logs eat the disk space, check and configure the retention
- A security problem requires analysing the recent connections
The systemd journal
journald (systemd-journald) collects the logs from three sources,
and knowing them tells you where a missing message could have been lost:
| Source | Content |
|---|---|
| Kernel messages | Equivalent of dmesg |
| Syslog | Messages from services through the /dev/log socket |
| stdout/stderr | Output of the services managed by systemd |
The journal is stored in binary form in /var/log/journal/ (persistent)
or /run/log/journal/ (volatile, lost at reboot).
# Check the space used by the journaljournalctl --disk-usageArchived and active journals take up 632.5M in the file system.Journal persistence
Under the default setting Storage=auto, the journal becomes
persistent as soon as /var/log/journal/ exists:
# Check that the directory existsls -ld /var/log/journal/If the directory does not exist, the journal is volatile (lost at reboot). To enable persistence, you need four steps, and the last one is the one people forget:
sudo mkdir -p /var/log/journalsudo systemd-tmpfiles --create --prefix /var/log/journalsudo systemctl restart systemd-journaldsudo journalctl --flushThe rule "the directory exists so the journal is persistent" only holds under
Storage=auto. With Storage=volatile, the journal goes back to memory
despite the presence of /var/log/journal/. The three values behave
differently:
Storage= | Behaviour |
|---|---|
auto (default) | Persistent if /var/log/journal/ exists, volatile otherwise |
persistent | Persistent, the directory is created if needed |
volatile | Always in memory, even if the directory exists |
To stop depending on the existence of a directory, set Storage=persistent
explicitly (see the Configuration section).
Read the logs
Without any filter, journalctl pours out the whole retained journal, from the
oldest entry to the most recent, in a pager (less) that you quit with
q. The options that follow reduce this flow to what interests you: a precise
boot, a systemd unit, a time window. They add up in the same
command, each extra option narrowing the result a little more.
Logs of the last boot
journald numbers the boots of the system: 0 is the current boot, -1
the previous one, and so on into the past. The -b option limits the
output to a given boot, which isolates the messages of an incident without
going through the whole history. --list-boots shows the retained boots
with their identifier and time range; if this list holds only one entry under
the header, the journal is not persistent.
# All the logs since the last bootjournalctl -b
# Logs of the previous bootjournalctl -b -1
# List the available bootsjournalctl --list-bootsExcerpt measured on Ubuntu 24.04 (systemd 255), the two boots before the current one:
IDX BOOT ID FIRST ENTRY LAST ENTRY -2 6e42eed67c254fb1874f9cdb85918c14 Sat 2026-09-05 20:57:11 CEST Sun 2026-09-06 22:37:56 CEST -1 9254bc00425b4becac188df6401fd188 Sun 2026-09-06 22:38:28 CEST Mon 2026-09-14 08:53:02 CESTLogs of a service
The -u option filters on a systemd unit, that is the name of the
service as systemd knows it (nginx.service, which you can shorten to
nginx). Repeating -u combines several units and shows their messages
interleaved in chronological order, which helps to correlate a web server
and its database during the same incident. If the output shows No entries,
the unit name is almost always the cause.
# Logs of nginxjournalctl -u nginx
# Logs of nginx since the last bootjournalctl -u nginx -b
# Logs of several servicesjournalctl -u nginx -u postgresqlFollow the logs in real time
The -f option (follow) does not return: it shows new entries as the
services write them, until Ctrl+C. It is the mode to start in a second
terminal before restarting a service, to see the error at the precise
moment it happens. The options combine, -fu nginx follows a single
service live.
# Equivalent of tail -fjournalctl -f
# Follow a specific servicejournalctl -fu nginxFilter by date
--since and --until bound the search in time. They accept an
absolute date in the YYYY-MM-DD HH:MM format as well as a relative
expression in English (1 hour ago, 30 min ago, yesterday, today),
interpreted against the current time of the machine. If you leave out
--until, the upper bound is now. It is the fastest way to frame an incident
whose approximate time you know.
# Since a datejournalctl --since "2026-06-28 08:00"
# Until a datejournalctl --until "2026-06-28 12:00"
# Relativejournalctl --since "1 hour ago"
# The last 30 minutesjournalctl --since "30 min ago"
# Yesterdayjournalctl --since yesterday --until todayFilter by priority
The journal uses the syslog priority levels (0 = the most critical,
7 = the most verbose), and -p keeps a level and everything more
severe than it:
| Level | Name | Meaning |
|---|---|---|
| 0 | emerg | System unusable |
| 1 | alert | Immediate action required |
| 2 | crit | Critical error |
| 3 | err | Error |
| 4 | warning | Warning |
| 5 | notice | Normal but significant |
| 6 | info | Information |
| 7 | debug | Debug (very verbose) |
# Errors and above (0 to 3)journalctl -p err
# Errors since the last bootjournalctl -p err -b
# Warnings and above for a servicejournalctl -u nginx -p warningSearch the logs
--grep applies a regular expression to the MESSAGE field of the
entries, without piping into the grep command. The filtering happens while
reading the binary journal, so faster, and the metadata of each entry stay
usable. The search is case sensitive as soon as the pattern contains an
uppercase letter, and insensitive otherwise; --case-sensitive=no forces
insensitive matching in every case.
# Text search (built-in grep)journalctl -b --grep "error|fail"
# Pattern with an uppercase letter, but matching forced case insensitivejournalctl -b --grep "Timeout" --case-sensitive=noKernel logs
The -k option (alias --dmesg) restricts the output to the messages
emitted by the kernel: hardware detection at boot, device errors, out
of memory, packets dropped by the firewall. Where dmesg numbers its lines in
seconds since boot, journalctl stamps them with the real date and time,
which lets you cross-check them with the application logs. Adding -b targets
the current boot, essential after a hardware incident.
# Equivalent of dmesg with timestampsjournalctl -k
# Kernel messages of the current bootjournalctl -k -bOutput formats
journalctl offers several formats with the -o option, from the
classic syslog line to JSON for a collection tool:
| Format | Use |
|---|---|
short | Default format (similar to syslog) |
short-precise | With microseconds |
verbose | Every structured field |
json | JSON (for automated parsing) |
json-pretty | Indented JSON (human reading) |
cat | Only the message (no metadata) |
# Verbose format (every field); the service is called crond on RHELjournalctl -u cron -n 1 -o verboseExcerpt measured on Ubuntu 24.04 (systemd 255): an entry carries many more fields, only the most useful ones are kept here, and the identifiers of the header are shortened.
Wed 2026-09-30 05:17:01.633361 CEST [s=891bfc9a…;i=344b68;b=0dbd62a1…;m=13…] PRIORITY=6 _TRANSPORT=syslog SYSLOG_FACILITY=10 SYSLOG_IDENTIFIER=CRON _COMM=cron _SYSTEMD_UNIT=cron.service MESSAGE=pam_unix(cron:session): session closed for user root _PID=2760268The fields prefixed with an underscore (_PID, _COMM, _SYSTEMD_UNIT)
are added by journald itself: the program that writes cannot forge them.
The others, such as MESSAGE or SYSLOG_IDENTIFIER, come from the
sender. The filters rely on these fields: journalctl _PID=2760268 or
journalctl _COMM=cron work directly.
# JSON format for a centralisation tooljournalctl -u nginx -n 5 -o jsonjournald configuration
The main file is not in the same place depending on the distribution. On
Debian 13 and Ubuntu 24.04, /etc/systemd/journald.conf exists, with
all its options commented out. On AlmaLinux 10, so RHEL 10, it does not
exist: the shipped file is /usr/lib/systemd/journald.conf. In both cases,
the safe method is the same, drop in a drop-in:
sudo mkdir -p /etc/systemd/journald.conf.dsudo nano /etc/systemd/journald.conf.d/99-retention.confEssential options
The file follows the INI format: the [Journal] section first, then one
option per line. A line commented out with # leaves the option at its
default value compiled into systemd. The settings below cover the four
decisions to make on a server: where to store the journal, what size to
grant it, how long to keep it, and at what rate to accept the messages of a
chatty service.
[Journal]# Persistence of the journal (persistent, volatile, auto, none)Storage=persistent
# Maximum size of the journalSystemMaxUse=500M
# Maximum size per fileSystemMaxFileSize=50M
# Maximum retention timeMaxRetentionSec=3month
# Compression of the old filesCompress=yes
# Rate limiting, here the default values (messages per interval and per service)RateLimitIntervalSec=30sRateLimitBurst=10000| Option | Effect |
|---|---|
Storage=persistent | The journal survives reboots |
SystemMaxUse | Maximum total size of the journal |
SystemMaxFileSize | Maximum size of each journal file |
MaxRetentionSec | Maximum retention time |
RateLimitBurst | Maximum number of messages per interval (per service) |
After a change, restart and not reload:
sudo systemctl restart systemd-journaldsystemd-journald cannot reload on the fly, and says so:
Failed to reload systemd-journald.service: Job type reload is not applicablefor unit systemd-journald.service.SystemKeepFree can cancel your SystemMaxUse
The classic trap of this section: SystemMaxUse sets a ceiling, but
SystemKeepFree reserves an amount of free space on the file system, and
the stricter of the two constraints wins.
Measured on a root offering 16,434 MB free, with SystemMaxUse=200M and
SystemKeepFree=16500M:
System Journal (/var/log/journal/4a8981f6...) is 16M, max 16M, 0B free.The effective ceiling drops to 16 MB, while the configuration asked for
200. Without the SystemKeepFree line, the same machine announces max 1.8G.
If your journal stays stubbornly tiny, this is the first thing to check.
Clean up the journal
The --vacuum-* options delete archived journal files, never the
active file being written. The cleanup is therefore immediate but partial: if a
single big active file takes the space, force a rotation first with
journalctl --rotate. These commands need root rights and have no lasting
effect, the journal will grow again as long as SystemMaxUse is not set in the
configuration.
Reduce by size
--vacuum-size deletes the oldest archived files until the journal takes at
most the requested size. The command lists each deleted file then
announces the space freed, which leaves you a trace of what you have just
permanently lost.
# Reduce the journal to 200 MBsudo journalctl --vacuum-size=200MReduce by age
--vacuum-time reasons on the date of the entries and not on the volume:
any archived file whose last entry is older than the given delay is deleted.
The accepted units go from the second to the year (s, m, h, days,
weeks, months, years). It is the variant to prefer when a retention
policy imposes a keeping period rather than a size.
# Delete the logs older than 2 weekssudo journalctl --vacuum-time=2weeksReduce by number of files
--vacuum-files keeps only the N most recent journal files, without
considering their size or their age. This criterion only makes sense if the
files have a consistent size, so if SystemMaxFileSize is set. The three
--vacuum-* options can appear in the same command, the strictest
constraint wins.
# Keep only the last 5 filessudo journalctl --vacuum-files=5# Check the space after cleanupjournalctl --disk-usagePermissions
The full journal is readable by root and by the systemd-journal group,
owner of the files. On top of that come ACLs set by systemd-tmpfiles at
boot, measured in /usr/lib/tmpfiles.d/systemd.conf: the adm group on
Debian 13, Ubuntu 24.04 and AlmaLinux 10, plus the wheel group on
AlmaLinux 10. For an account that must only read the logs, the dedicated group
remains the right choice:
# Add a user to the groupsudo usermod -aG systemd-journal my-userUnprivileged users only see the logs of their own services (user timers and services).
Journal and security
Two journald.conf settings concern using the journal in an audit context.
The first makes any later change to the journal detectable, including by an
attacker who obtained root rights. The second keeps feeding an existing
syslog chain, for example to ship the messages to a remote collector the
attacker does not control.
Check integrity
journald can seal the persistent files to make any tampering
detectable. The Seal= option is already on by default (journald.conf(5)
manual, systemd 257), but it does nothing without a sealing key: it is
journalctl --setup-keys that creates it. The command produces a key that
stays on the machine and a verification key to keep elsewhere, otherwise
a root attacker could re-sign everything.
# Create the key pair (persistent journal required)sudo journalctl --setup-keys
# Later, check the integrity with the key kept off the machinesudo journalctl --verify --verify-key=<verification key>Forward to syslog
To run journald alongside a traditional syslog server (rsyslog,
syslog-ng), the ForwardToSyslog= option forwards every message to the syslog
socket. It is off by default upstream (journald.conf(5) manual,
systemd 257), but Debian 13 and Ubuntu 24.04 already turn it on through
their syslog.conf drop-in. On AlmaLinux 10, which ships none, you set it
yourself:
[Journal]ForwardToSyslog=yesTroubleshooting
The symptoms below cover almost every problem met with the journal: nothing
left after a reboot, service not found, dropped messages, full disk. Read the
Probable cause column before applying the solution, several symptoms look
alike without sharing the same origin. Two commands settle most cases:
journalctl --list-boots to know whether the journal is persistent, and
systemctl status systemd-journald to check that the daemon runs.
| Symptom | Probable cause | Solution |
|---|---|---|
| Empty journal after reboot | Persistence not configured | Create /var/log/journal/, Storage=persistent, then journalctl --flush |
/var/log/journal/ exists but the journal stays volatile | journalctl --flush forgotten, or Storage=volatile | Run --flush; check systemd-analyze cat-config systemd/journald.conf |
Journal capped well below SystemMaxUse | SystemKeepFree reserves more free space than available | Read the is …, max …, … free line at service start |
| Setting without effect, no visible error | Misspelled key, silently ignored | journalctl -u systemd-journald right after the restart |
No entries for a service | Wrong unit name | Check with systemctl list-units | grep name |
Suppressed N messages | Rate limiting active | Raise RateLimitBurst or fix the service |
| Journal too big | No limit configured | Configure SystemMaxUse and MaxRetentionSec |
| User cannot see the logs | Not in the group | sudo usermod -aG systemd-journal user |
| Logs of previous boots missing | Volatile journal | Enable Storage=persistent |
# Quick diagnosisjournalctl --disk-usage # Space usedjournalctl --list-boots # Available bootsjournalctl -p err -b # Errors of the current bootsystemctl status systemd-journald # State of the daemonBest practices
These reflexes avoid the two most frequent troubles with journald: a
journal emptied at reboot just when you needed it, and a /var partition
filled by logs without limit. They are set when the server is installed,
not during the incident, because none of these settings rebuilds the
entries already lost.
- Always enable persistence: without
/var/log/journal/, the logs are lost at reboot - Configure
SystemMaxUse: keep the journal from filling the disk (200-500 MB is a good compromise) - Configure
MaxRetentionSec, 1 to 3 months depending on compliance needs - Start the diagnosis with
journalctl -p err -b: the errors of the current boot are the starting point - Use
-u serviceto target a service, do not go through the whole journal - Schedule the cleanup,
journalctl --vacuum-sizeor--vacuum-timeregularly - Centralise the logs in production, journald alone is not enough for an audit or a multi-server correlation
Practice
A journal that disappears at reboot can only be observed by really rebooting.
This lab gives you a VM with a volatile journal: you enable
Storage=persistent, create /var/log/journal/ and prove after a
reboot that the previous boots stay readable with
journalctl --list-boots.
Key points
- journalctl reads the binary journal of systemd, it is the main diagnosis tool on Linux
- Filter by service (
-u), by priority (-p), by date (--since) and by boot (-b) to target the useful information - Persistence needs
/var/log/journal/,Storage=persistent, and above alljournalctl --flushthat everybody forgets - On RHEL and AlmaLinux,
/etc/systemd/journald.confdoes not exist: go through a drop-in underjournald.conf.d/, otherwise you mask the shipped file (and itsAudit=line) SystemKeepFreecan cancelSystemMaxUse: the stricter constraint wins- Priorities go from 0 (emerg) to 7 (debug), start with
-p errwhen a problem occurs - SystemMaxUse and MaxRetentionSec control the size and the retention of the journal
journalctl --vacuum-sizeand--vacuum-timeclean up by hand- The JSON format (
-o json) eases centralisation to Loki, Elasticsearch or Graylog
Knowledge check
Check that the essentials of this guide stuck. The questions cover only what has just been explained here.
Knowledge check
Check what you have learned with this interactive quiz
How it works
- The timer starts when you click Start
- Multiple choice, true/false and short answer questions
- You can move between questions
- Detailed results are shown at the end
Starts the quiz and the timer
Review
(0/0)Score by domain
A domain groups several guides and gets several questions, so its score is more reliable than a per-topic score, which often rests on a single question.
Summary
Strengths
No topic passed at 80% on at least two questions yet.
Weak areas
No topic below the pass mark.
Recommended next
Hints before you try again?
Continue the course: Configure logrotate to manage log rotation on Linux
A new full quiz with random questions
Work again only on the questions you missed