Blameless Post‑Mortem: Self‑Inflicted Firewall Outage
What a blameless post‑mortem is
A structured review that answers what happened, why it happened, and how to prevent recurrence without assigning individual fault. It surfaces systemic gaps—broken assumptions, missing safety rails, detection or response deficiencies—while preserving a psychologically safe environment for engineers.
Why it matters in infrastructure‑heavy environments
- Encourages full disclosure → richer root‑cause analysis.
- Highlights missing safeguards (pre‑change validation, automated rollback) that stay hidden in blame‑centric cultures.
- Drives concrete improvements to change‑management pipelines, verification gates, and blast‑radius controls.
- Aligns with SRE/DevOps principles: treat failure as a learning opportunity.
Incident Overview
| Timestamp (UTC) | Event |
|---|---|
| 2025-09-20 02:10:12 | CI pipeline pushed a new nftables ruleset to all 12 edge firewalls via SSH. |
| 2025-09-20 02:14:03 | Alerts: loss of SSH, BGP peers flapped, downstream health checks failing. |
| 2025-09-20 02:14:45 | On‑call engineer’s SSH reconnection timed out. |
| 2025-09-20 02:16:10 | IPMI console obtained; inet filter table replaced with a rule set lacking an established,related accept rule and default policy drop. |
| 2025-09-20 02:20:05 | Restored previous ruleset from nightly backup (nft -f /var/backups/nftables/2025-09-19.nft). |
| 2025-09-20 02:28:30 | SSH connectivity, BGP peers, and service traffic restored. |
| 2025-09-20 02:45:00 | Incident declared resolved; post‑mortem scheduled. |
Technical details
-
OS/Kernel: Linux 5.15, nftables v1.0.4.
-
Change mechanism:
ssh root@fwX "nft -f /opt/nftables/production.nft" -
Problematic script excerpt
flush table inet filter delete table inet filter add table inet filter add chain inet filter input { type filter hook input priority 0 \; policy drop \; } add chain inet filter forward { type filter hook forward priority 0 \; policy drop \; } add chain inet filter output { type filter hook output priority 0 \; policy accept \; } # Intended to only add SSH rule add rule inet filter input tcp dport 22 ip saddr 10.0.0.0/8 accept -
Critical flaw: The script began with
flushthendelete, removing the existinginet filtertable and its implicitct state established,related acceptrule. The newly createdinputchain defaulted todrop, so all established traffic—including the SSH sessions used to push the change—was dropped. -
Blast radius: 100 % of the firewall fleet lost in‑band management; downstream load‑balancer health checks failed, causing brief service degradation.
Initial response & containment
- Detection – Prometheus alerts (
ssh_down,bgp_peer_down) fired within 20 s. - Isolation – Engineers switched to out‑of‑band IPMI consoles.
- Rollback – Re‑applied the last known good ruleset from a nightly Git‑backed backup via
nft -f. - Verification – Diffed
nft list rulesetagainst the backup, confirmed equality, then validated SSH and BGP. - Communication – Incident channel updated every 5 min; a temporary “firewall‑maintenance” banner warned ops teams.
Broken Assumption
Assumption: The CI‑driven nft -f push would be purely additive—only inserting the SSH allow rule while leaving the existing rule set intact.
Basis:
- Prior experience with
iptables -Astyle scripts that append. - Belief that
nft -fbehaved similarly when the script lacked explicitdelete/flush. - No code review that would have caught the destructive lines.
How it caused the outage
flush table inet filter+delete table inet filterremoved the existing table and all its chains.- A new table was created with a default
droppolicy on theinputchain. - Only a single SSH allow rule was added; it matched new SYN packets from
10.0.0.0/8but not existing ESTABLISHED traffic. - Established sessions (including the SSH channel used to push the change) were dropped instantly.
Lessons
- Never assume additive behavior for a tool that can replace state; verify exact semantics.
- Treat any
flush,delete, or table recreation as a destructive operation requiring explicit approval. - Enforce a “no‑destructive‑change‑without‑review” rule in CI (block scripts containing
flush/deleteunless a special flag is present). - Validate the full resulting ruleset, not just the intended delta, before committing to production.
Missing Safety Rails
| Step | Current Practice | Gap |
|---|---|---|
| Authoring | Engineers wrote nft scripts in Git; no linting for destructive ops. | No automated detection of flush/delete. |
| Review | Peer review focused on functional intent; no safety checklist. | Missing item: “Does the script contain any table‑level destructive operations?” |
| Pre‑change validation | None; script sent directly via SSH. | No syntax check (nft -c), no dry‑run, no diff against current state. |
| Canary / staged rollout | All devices updated simultaneously. | No incremental exposure; blast radius = 100 %. |
| Verification gate | Post‑change manual SSH test (failed because of outage). | No automated, pre‑emptive verification (connectivity, traffic flow). |
| Rollback trigger | Manual restore from backup after outage detected. | No automatic detection‑and‑rollback mechanism. |
| Blast‑radius control | No limits; change applied to entire fleet. | No mechanism to restrict change to a subset. |
| Operator intervention point | Only after total loss of connectivity (out‑of‑band needed). | No early‑warning or manual abort option. |
Key missing rails
- Pre‑change syntactic & semantic validation (
nft -c+ diff‑based dry‑run). - Change‑scope limitation (canary hosts, feature flags, or time‑bounded windows).
- Automated verification (connectivity test, traffic‑plane validation, BGP peer state).
- Automatic rollback on verification failure.
- Blast‑radius controls and an explicit operator abort point.
Safer Remote‑Change Pattern for nftables
Adopt a transaction‑oriented workflow that validates, limits exposure, and can roll back automatically.
1. Prepare a immutable backup
# On each target firewall (or via a central orchestration tool)
nft list ruleset > /var/backups/nftables/$(date +%F_%T).nft
2. Validate the proposed changeset locally
# Syntax check
nft -c -f /opt/nftables/proposed.nft
# Dry‑run: load into a temporary table and diff against current state
nft -f /opt/nftables/proposed.nft -I netdev # load into a temporary namespace if supported
nft list ruleset > /tmp/proposed.nft
diff -u /var/backups/nftables/current.nft /tmp/proposed.nft
If the diff shows only the intended additions, proceed; otherwise abort.
3. Canary rollout
- Apply the changeset to a small subset (e.g., 1‑2 firewalls) using the same
nft -fcommand. - Run automated verification on the canary hosts:
# Example verification script ssh fw-canary "nft list ruleset | grep -q 'tcp dport 22 accept' && \ ping -c 3 8.8.8.8 && \ bgpctl show summary | grep -q 'Established'" - If verification fails, trigger an automatic rollback on the canary hosts (see step 5) and abort the fleet‑wide push.
4. Fleet‑wide apply with transaction boundaries
Use nft’s built‑in transaction semantics: the -f flag loads the entire file as a single atomic transaction.
ssh root@fwX "nft -f /opt/nftables/proposed.nft"
Because the load is atomic, either the whole ruleset replaces the old one or nothing changes if an error occurs.
5. Automatic rollback trigger
- After applying, run the same verification script on each host.
- If verification fails, execute:
ssh root@fwX "nft -f /var/backups/nftables/$(ls -t /var/backups/nftables/ | head -1)" - Orchestration tools (Ansible, Salt, etc.) can wrap steps 2‑5 in a playbook that aborts on the first verification failure and runs the rollback task.
6. Post‑change validation & cleanup
- Diff the final ruleset against the backup to confirm no drift.
- Retain the backup for a defined retention period (e.g., 30 days) for audit purposes.
- Update documentation and CI lint rules to flag any
flush/deletestatements unless explicitly whitelisted.
Summary of improvements
- Atomic transactions via
nft -fguarantee a clean all‑or‑nothing apply. - Pre‑change validation (
nft -c+ diff) catches destructive operations before they reach devices. - Canary rollout limits blast radius and provides early feedback.
- Automated verification replaces manual SSH checks with scripted, repeatable tests.
- Automatic rollback on verification failure eliminates manual MTTR delays.
- Explicit backup/restore gives a known‑good restore point and audit trail.
Implementing this pattern transforms a risky, additive‑assumption‑driven push into a safe, verifiable, and self‑healing change process for nftables‑based firewalls.