Where to start First steps track · 15 stages
Linux Labs

Postmortem — a blameless outage review

10 min Ubuntu 24.04 · systemd 255

A postmortem is a written outage review: timeline, impact, root cause and action items, so the outage does not repeat.

Cheat sheet

6 commands Printable version
  • cd ~/incident
  • outage window: -S from when, -U until when (today, UTC)
  • sudo journalctl -S 12:00 -U 12:10 -o short-iso -t sudo > raw.txt
  • the service in a separate call: -t with -u gives empty output
  • sudo journalctl -S 12:00 -U 12:10 -o short-iso -u nginx >> raw.txt
  • by time, into the "HH:MM:SS event" format
  • sort raw.txt | cut -c12-19,26- > timeline.txt
  • the first and the last error users saw
  • awk '$9 >= 500' /var/log/nginx/access.log | sed -n '1p;$p'
  • unix time from $msec into clock time
  • date -u -d @1792677901 +%T

Overview

A postmortem (outage review) is a document written after an incident (incident-response), once the service works again. It has four parts: what happened minute by minute, what users felt, why it happened and what the team will change. The goal is that the same outage does not repeat and that everyone who was not on shift gets its lessons.

markdown
# Review: the site was down on 22.10

### Timeline
12:00:40 a release changed the site config; nginx reload failed
12:00:52 scheduled nginx restart — it did not start:
         unknown directive "proxy_pas"
12:00:53 lab-sitecheck: the site does not answer — outage starts
12:01:21 lab-sitecheck: the site answers 200 — outage ends

### Impact
For 28 s the site answered nobody: 0.2 % of the 30-day error
budget at SLO 99.5 %.

### Root cause
A typo in the site config. The release did not run `nginx -t`,
nobody noticed the failed reload, and the restart stopped the site.

### Action items
- The release runs `nginx -t` and stops on an error.
- An alert on a failed nginx reload.

In Russian the sections are called Хронология, Влияние, Причина and Действия.

Timeline

The timeline is the outage events in time order, a line per event: UTC time first, then a fact. The format is the same as the timeline kept during the incident (incident-response):

text
HH:MM:SS event

If nobody kept it, the timeline is assembled from the logs. Each source knows its own part:

Source What it shows
journalctl -t sudo what people did: who, when, which command
journalctl -u <service> what the program did: start, error, stop
site check, access.log what users saw: codes and response times
alerts when the team learned about the outage

The start and end of the outage are taken from what users saw: the first error in access.log or in the site check, and the first good response after them. The time of the edit is a separate line. Between an edit and the outage start there may be seconds or days: a typo waits in the file until the service is restarted. Journal lines go into the timeline as they are (-o short-iso), only trimmed to the fact.

Blameless

A blameless review starts from the fact that people always make mistakes, and the system has to catch a mistake before users do. The review asks what let the mistake reach them: which check was missing, what was not visible, why the rollback took ten minutes.

So the review uses roles instead of names: "the release", "the on-call", deploy. A person named as the culprit will keep quiet about their next mistake, and the next outage will take longer to find.

Root cause

An outage rarely has a single cause. Usually it is a pair:

  • a latent defect — something broken in advance and waiting: a typo in a config, a disk 95 % full, an expired certificate;
  • a trigger — the event that made the defect visible: a restart, a load spike, the nightly rotation.

A chain of "why" leads to them: the site was down — nginx did not start — the config had a typo — the release did not check the config — the release has no such check. Stop the chain at something the team can change. The root cause section names the broken components: the nginx site config, pg_hba.conf, the alerting rule.

Impact

How long the outage lasted (as users saw it), how many requests failed, what share of the error budget it ate (sli-slo). The numbers come from access.log for the outage window — see the recipe below.

Action items

Action items are what the team will change so the outage does not repeat or is found sooner. Each one is concrete and checkable, with an owner and a deadline:

Vague Checkable
be more careful with releases the release runs nginx -t and stops on an error
keep an eye on the disk an alert when /var is over 80 % full
test better a config test in the config repository CI

"Be more careful" cannot be checked, and in a month the outage repeats.

Recipes

3

A timeline from the journals

bash
cd ~/incident
since='2026-10-22 12:00'; until='2026-10-22 12:10'
sudo journalctl --since "$since" --until "$until" -o short-iso \
  -t sudo -t lab-sitecheck > raw.txt
sudo journalctl --since "$since" --until "$until" -o short-iso \
  -u lab-app >> raw.txt
sort raw.txt | cut -c12-19,26- > timeline.txt

Two calls: -t and -u in one command select entries that match both conditions at once, and the output goes empty. -o short-iso prints the date and time with the zone (2026-10-22T12:00:41+00:00), and sort merges the two lists by it. cut keeps the time (characters 12–19) and everything after the zone: the host, the program and the message.

text
12:00:41 node1 sudo[1237]:  deploy : … COMMAND=/usr/bin/sed -i …
12:00:43 node1 lab-sitecheck[812]: site answers 502

Then edit the file by hand: drop the noise (the pam_unix lines of sudo), roles instead of names, mark "outage starts" and "outage ends".

Outage start and end from access.log

bash
awk '$9 >= 500' /var/log/nginx/access.log | sed -n '1p;$p'

The first and the last line with a 5xx. If the line ends with $msec (sli-slo), date -u -d @1792677901 +%T turns it into a time (date). The outage ends with the first good response after the last error.

How many requests failed

bash
f=$(date -d '2026-10-22 12:00:53' +%s)
t=$(date -d '2026-10-22 12:01:21' +%s)
awk -v f="$f" -v t="$t" '$NF >= f && $NF < t {n++; if ($9 >= 500) b++}
  END {print n, b}' /var/log/nginx/access.log

All requests in the outage window and the 5xx among them. The budget share is b ÷ the SLO window budget (sli-slo).

Troubleshooting

3

Gaps in the timeline

  • The journal is already cleaned up: journalctl --disk-usage and the earliest entry (journalctl | head -n 1) show what is left.
  • The service writes to its own file instead of the journal: look in /var/log/<service>/.
  • Machine clocks drift or zones differ: when merging logs from two machines, convert everything to UTC (journalctl --utc).

The review boiled down to "a person erred"

Ask the next "why": why did the mistake reach users, why did no check catch it, why did the rollback take that long. Its answer gives an action item.

Action items do not get done

The item has no owner or deadline. Every item is a ticket with a name and a date; the next review checks what got done.

Danger zone

1

Names in the root cause

A review with a culprit teaches the team to hide mistakes. The next outage takes longer to investigate: nobody will say they edited the config a minute before it. Roles instead of names, questions about the system instead of questions about a person.

Terms

3
timeline
Outage events in time order, a line per event: "HH:MM:SS event" in UTC. Kept during the incident or assembled from the logs afterwards. Also appears in: Incident response, journalctl
blameless
A review rule: people always make mistakes; the question is what let the mistake reach users. The review uses roles instead of names. Also appears in: Incident response
action item
What the team will change after an outage: a concrete, checkable task with an owner and a deadline — "nginx -t in the release"; "be more careful" does not qualify.

See also

  • incident-response — the first minutes, runbook, live timeline.
  • sli-slo — the error budget for the impact section.
  • journalctl — time filters, -t, -u, -o short-iso.
  • nginx — access.log: codes and response times.
  • date — unix time to clock time and back.
  • sed-awk — fields and counting in awk.
Loading…