Skip to main content
RunBook Academy

LinuxLXXIX · Troubleshooting MethodologyThe loop

Evidence-based diagnosis - capture before you disturb

Intermediate⏱ ~12 min🧪 Lab requiredbashjournalctldmesgsystemctlsslsofcoredumpctl

What you'll learn

  • Capture volatile state before a restart destroys it
  • Build a timestamped evidence bundle that can be handed over
  • Read journald with the filters that answer a specific question
  • Separate observation from inference, and correlation from causation

Prerequisites

Verified against Ubuntu 24.04 LTS · Debian 12 (Bookworm) · RHEL 9.x · Rocky Linux 9.x · AlmaLinux 9.x · Linux kernel 6.1 LTS / 6.6 LTS · systemd 255+ · OpenSSH 8.7p1 (RHEL 9) / 9.6p1 (Ubuntu 24.04) · nftables 1.0.x · chrony 4.x · Pacemaker 2.1.x · Corosync 3.1.x · 2026-08-10

Not yet marked complete on this device.

Step 4 of the troubleshooting loop is where most investigations are won or lost. The evidence that explains an incident is usually volatile: it lives in process memory, in open file descriptors, in socket state, and in a journal that is about to rotate. A restart removes all of it in under a second.

This lesson is the practical detail behind that one step.

Volatile first, durable second

Order your capture by how fast the evidence disappears.

  1. Volatile and lost on restart: process list, memory maps, open files, socket state, thread stacks
  2. Volatile and lost on reboot: the current boot journal, dmesg ring buffer, tmpfs contents, cgroup counters
  3. Volatile and lost on rotation: older journals, application logs, audit logs
  4. Durable: configuration files, package history, monitoring time series, the change ticket

If you only have thirty seconds before someone restarts the service, spend them on the first line.

The capture

Write everything into one timestamped directory. A capture scattered across a scrollback buffer is not evidence - it cannot be handed over, attached to a ticket, or re-read a week later during the review.

Read-only / Safeset up the bundle
$ INC=/var/tmp/inc-$(date -u +%Y%m%dT%H%M%SZ); mkdir -p $INC; PID=$(systemctl show -p MainPID --value myapp.service)
(no output; $INC is the bundle and $PID is the process under investigation)

Illustrative output

Read-only / Safevolatile state
$ ps auxf > $INC/ps.txt; ss -tanp > $INC/sockets.txt; ls -l /proc/$PID/fd | wc -l > $INC/fdcount.txt; cat /proc/$PID/status > $INC/status.txt; cat /proc/$PID/limits > $INC/limits.txt; cat /proc/$PID/stack > $INC/kstack.txt 2>/dev/null; lsof -p $PID > $INC/lsof.txt
(no output; all of these are read-only inspections of live state)

Illustrative output

Read-only / Safeboot-scoped state
$ journalctl -b --no-pager > $INC/journal-boot.txt; journalctl -b -1 --no-pager > $INC/journal-prevboot.txt 2>/dev/null; dmesg -T > $INC/dmesg.txt; systemctl status myapp -l --no-pager > $INC/unit.txt; df -h > $INC/df.txt; df -i > $INC/df-inode.txt; free -m > $INC/free.txt; uptime > $INC/uptime.txt
(no output; the bundle now holds the boot-scoped evidence)

Illustrative output

Reading the journal with intent

Unfiltered journalctl output is not evidence, it is volume. Every query should answer a stated question.

Read-only / Safeone unit, one window
$ journalctl -u myapp --since '2026-08-10 13:50' --until '2026-08-10 14:30' -o short-precise --no-pager
Aug 10 14:10:02.114338 web01 myapp[2214]: WARN pool exhausted, waiting for connection
Aug 10 14:11:03.882901 web01 myapp[2214]: FATAL pool wait timeout after 60s

Illustrative output

Read-only / Safekernel-level evidence
$ journalctl -k -b -p err --no-pager; journalctl -b -p warning --no-pager | grep -iE 'oom|i/o error|read-only|segfault|timeout'
Aug 10 14:11:01 web01 kernel: Out of memory: Killed process 2214 (myapp) total-vm:8214512kB
Aug 10 14:11:01 web01 kernel: oom-kill: constraint=CONSTRAINT_MEMCG

Illustrative output

Useful filters worth memorising: -u for a unit, -b and -b -1 for this and the previous boot, -p for priority, --since and --until for a window, -k for the kernel, _PID= for a specific process, -f to follow, and -o json when you need to hand the result to a script.

Observation, inference, action

Keep the three apart in your notes and in the incident channel. Most bad calls under pressure come from an inference being relayed as an observation.

  • Observation: “df -i /var reports 100% inode use.”
  • Inference: “the mail spool is probably creating millions of small files.”
  • Action: “counting files under /var/spool to test that.”

Write observations with the command that produced them and the UTC time you ran it. When someone joins the call an hour later, that log is the handover.

Read-only / Safevalidate the timeline
$ timedatectl; chronyc tracking
               Local time: Mon 2026-08-10 14:35:12 UTC
System clock synchronized: yes
NTP service: active
Reference ID    : 0A0A0A0A (ntp1.example.com)
System time     : 0.000241 seconds fast of NTP time

Illustrative output

Negative evidence counts

“The backup job did not run” and “no configuration change was recorded in the window” are findings, and they should go in the bundle with the command that established them. Absence of a recorded change is the fact that rules out an entire class of hypothesis, and it is exactly the sort of thing that gets re-checked three times during a long incident because nobody wrote it down the first time.

Knowledge check

Knowledge check · 5 questions

  1. Q1. A failing service will probably recover if restarted, and management is asking for the restart now. What does evidence-based diagnosis require?

  2. Q2. A filesystem reports "No space left on device" but df -h shows 45% used. Which captured artefact explains it?

  3. Q3. Host A logs a timeout at 14:10:02 and host B logs a related failure at 14:09:58. What must you verify before concluding B caused A?

  4. Q4. A finding that nothing changed in the window is not evidence and does not belong in the bundle.

  5. Q5. Which items are lost by restarting the failing unit? Select all that apply.

Passing score: 75%. Answers are checked in this browser.