A Deadman Switch for Firewall Edits
The scariest line in homelab automation is the one that edits a firewall rule on the router you’re SSH’d in through.
Here’s how Claude Code and I edit them anyway.
The Fear
I needed to flip a UDM Pro rule named “Block inter-VLAN traffic” from accept to drop. The rule had been miswired for ages — set to accept, short-circuiting every per-flow allow above it — and closing it back down was the whole point. The orchestrator running the change was a NixOS VM on my home network. If the flip took out the path that VM (or my own SSH) was using, I’d be locked out of my own router with no console fallback.
The fix isn’t “be careful.” The fix is to make the dangerous edit auto-revert unless I confirm it’s fine.
You’ve seen this pattern before. Change your monitor’s resolution on macOS or Windows and the system applies it for fifteen seconds with a “Keep these display settings?” dialog ticking down. If your screen went black, you can’t click anything — and that’s the point. The default outcome is rollback. Confirmation is opt-in.
That’s exactly what we want for a firewall flip on a router we’re about to change the rules of.
The Trick
udm_set_firewall_rules.py --flip-with-revert 300 --apply
Read: “flip the rule, but auto-revert in 300 seconds unless I tell you not to.”
What it does, in order:
- Snapshot. GETs the current rule and stashes the full JSON payload on the UDM at
/root/.udm-flip-revert/payload-<id>.json. - Stage the revert. Writes a tiny shell script next to the payload that re-PUTs the snapshot to the UDM API.
- Schedule the revert.
systemd-run --on-active=300s --unit=udm-flip-revert-<id>queues the script as a transient timer. - Flip. PUTs the new rule (
action: drop).
Now you have a 5-minute window to verify:
✓ flipped 'Block inter-VLAN traffic' to action='drop'
⏱ auto-revert scheduled (~300s)
TO KEEP THE FLIP: ssh udm 'systemctl stop udm-flip-revert-66c1d…timer'
TO ROLL BACK NOW: ssh udm 'systemctl stop …timer; bash /root/.udm-flip-revert/revert-….sh'
WAIT IT OUT: do nothing — timer will revert in ~300s
Three outcomes:
- It worked. Stop the timer. Flip is permanent.
- It broke something. Stop the timer, run the revert script. Or just stop typing — the timer fires in N seconds and the rule comes back on its own.
- It locked me out. Same. The timer doesn’t need me to fire.
The third case is the whole reason this exists.
Why systemd-run, Not at(1)
UDM Pro (Debian 11) doesn’t ship atd. It does have systemd. systemd-run --on-active=Ns creates a transient timer that fires once and self-collects, which is exactly the shape of “do this thing in N seconds and then go away.”
ssh_udm(
f"systemd-run --on-active={seconds} --unit={unit} --collect "
f"--quiet /bin/bash {script_file}"
)
--collect is the key flag — without it the unit lingers in failed/inactive state and clutters systemctl.
The Caveat
Transient systemd units live in /run, which is tmpfs. If the UDM reboots inside the revert window, the timer is gone — and the flip stays applied with no deadman watching it.
Counterintuitive but real: keep the window short. A 5-minute window with a small reboot risk is safer than a 1-hour window where a UPS hiccup at minute 12 leaves you with a permanent lockout. Long enough to verify, short enough that “did I forget about a pending revert?” is never a question.
Takeaway
The deadman pattern generalizes past UDM firewall rules. Anywhere you’re about to apply a change that could sever the path you applied it through — routing tables, sshd_config, nftables, BGP — the same shape works:
- Capture current state
- Stage a script that restores it
- Schedule the script to run in N seconds
- Apply the change
If you’re still there in N+ε seconds and things look good, cancel the timer. If not, the timer cancels you.
I sleep better with this in the toolbox.