LinuxLXXIX · Troubleshooting MethodologyThe loop
Evidence-based diagnosis - capture before you disturb
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
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.
- Volatile and lost on restart: process list, memory maps, open files, socket state, thread stacks
- Volatile and lost on reboot: the current boot journal, dmesg ring buffer, tmpfs contents, cgroup counters
- Volatile and lost on rotation: older journals, application logs, audit logs
- 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.
$ 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
$ 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
$ 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.
$ journalctl -u myapp --since '2026-08-10 13:50' --until '2026-08-10 14:30' -o short-precise --no-pagerAug 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 60sIllustrative output
$ 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_MEMCGIllustrative 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 /varreports 100% inode use.” - Inference: “the mail spool is probably creating millions of small files.”
- Action: “counting files under
/var/spoolto 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.
$ 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 timeIllustrative 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
Q1. A failing service will probably recover if restarted, and management is asking for the restart now. What does evidence-based diagnosis require?
Q2. A filesystem reports "No space left on device" but df -h shows 45% used. Which captured artefact explains it?
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?
Q4. A finding that nothing changed in the window is not evidence and does not belong in the bundle.
Q5. Which items are lost by restarting the failing unit? Select all that apply.
Passing score: 75%. Answers are checked in this browser.