Power Resilience¶
What happens to the Z2 Mini when the building loses power, and what has been done in software to shorten the outage, detect it, and prove nothing broke.
This page exists because of a real outage. On 2026-09-09 at 12:52 EDT the apartment lost power. The Z2 died instantly. It came back at 19:35 EDT — not because power returned, but because Gabriel walked in the door and pressed the button. 6 hours 43 minutes of downtime from an outage that lasted seconds.
What actually failed, and what didn't¶
The outage itself was the smaller problem. Three separate weaknesses turned it into most of a day:
| Weakness | Consequence | Fixed by |
|---|---|---|
| Machine set to stay off after AC loss | 6h43m of the 6h43m | BIOS After Power Loss (below) |
| No external monitoring | Nobody knew until someone physically looked | Dead-man's switch (below) |
cron silently skips missed jobs |
A backup during an outage window would never run and never warn | systemd timers with Persistent=true |
What held up well: every service came back on its own. All 19 containers restarted because each stack uses restart: unless-stopped and — critically — nothing had been docker stopped beforehand. See 09-maintenance.md for why an explicit docker stop breaks that.
No data was lost. PostgreSQL replayed its WAL, and all four SQLite databases passed PRAGMA integrity_check.
The hard limit¶
Software cannot keep a computer running without electricity, and it cannot press a power button.
Everything on this page is about the three things software can do: come back faster, tell you it happened, and prove nothing broke. Riding through an outage, or shutting down cleanly before a battery dies, needs a UPS — the software half of that is NUT, but it needs hardware to talk to. Not currently installed.
1. Automatic power-on after AC returns¶
This is firmware, not software, and it recovers almost the entire outage window.
The Z2 Mini ships set to stay powered off when AC is restored. Change it:
- Power on and press F10 repeatedly to enter BIOS setup
- Advanced → Power Management Options
- Set After Power Loss to
Power On - F10 to save and exit
Why not do this from Linux? The setting lives in the chipset's
AfterG3_Enbit, reachable withsetpci -s 00:1f.0 A4.B. Don't bother — the BIOS rewrites that register at every POST, so the change does not survive a reboot. The firmware menu is the only durable place to set it.
Getting into the BIOS without catching F10¶
The firmware advertises EFI_OS_INDICATIONS_BOOT_TO_FW_UI, so Linux can request a reboot straight into setup — no key timing:
Run it at the machine, or over SSH with ssh -t to force a TTY. Without a TTY, sudo cannot prompt and fails with "a terminal is required to read the password" — and dropping the sudo just moves the failure to polkit.
Verifying the setting from Linux¶
You cannot durably set After Power Loss outside the firmware, but you can read it. The BIOS writes the chipset's AfterG3_En bit at every POST, so it reflects the current setting:
Bit 0 of the returned byte: 0 = powers on after AC loss (what you want), 1 = stays off. Confirmed 0x00 on 2026-09-09 after the setting was changed.
Also check uptime -s after the next power event — it should match when power returned, not when someone pressed the button.
2. Dead-man's switch — outbound heartbeat¶
Nothing self-hosted can tell you the Z2 is down. Beszel runs on the Z2, so it dies with the Z2. And nothing outside can poll in: every service is bound to the tailnet IP 100.67.235.68 with no inbound path from the public internet.
The only architecture that works is an outbound heartbeat where the absence of a ping is the alarm. The Z2 checks in every 5 minutes; when it stops, the external service notices and emails.
Script: /home/gabriel/scripts/heartbeat.sh, driven by z2mini-heartbeat.timer.
Setup¶
The timer is already installed and running, but stays a no-op until it has a URL:
- Sign up free at healthchecks.io
- Create a check named
z2mini— period 5 minutes, grace 10 minutes - Put its ping URL in the config file:
ssh gabriel@z2mini
nano ~/.config/z2mini/healthchecks-url
sudo systemctl start z2mini-heartbeat.service
sudo is required — without it systemctl start goes through polkit, which has no way to prompt you over a non-interactive SSH command and fails with "Interactive authentication required." The same trap applies to systemctl reboot --firmware-setup below.
- Confirm the check flips to green in the healthchecks.io dashboard
You'll get an email roughly 15 minutes after the Z2 goes dark — 5 for the missed ping, 10 for the grace period.
The URL file is mode
0600and lives outside the repo. A ping URL is a write-only token — anyone holding it can only mark your check as alive — but there is no reason to publish it.
3. Post-unclean-boot integrity check¶
After a hard power cut you want to know, without doing it by hand, whether the databases survived. z2mini-post-crash-check.service runs at every boot and answers that question by email.
Script: /home/gabriel/scripts/post-crash-check.sh
How it decides a crash happened: systemd writes a shutdown sequence to the journal on the way down. A power cut leaves none of it, so the script greps the tail of the previous boot for systemd-shutdown / Reached target Power-Off. Absent means unclean. On a clean boot the script exits silently and sends nothing.
What it checks when the previous boot was unclean:
| Check | Detail |
|---|---|
| Filesystems | ext4 orphan-cleanup / recovery / error lines from this boot |
| SQLite x4 | PRAGMA integrity_check on Vaultwarden, Beszel, Beszel-aux, Navidrome |
| PostgreSQL x2 | pg_isready on Immich and OpenProject, plus a log scan for corrupt / invalid page / checksum failed / PANIC |
| Containers | running count against the expected 19 |
| Backup | /mnt/backup still mounted, and the last line of backup.log |
The email subject states the verdict outright — all checks passed, or N PROBLEM(S) FOUND.
It waits 90 seconds after docker.service before judging anything, so Postgres has time to finish WAL replay and the stacks have time to come up.
Run it by hand any time:
ext4 "orphan cleanup" is not damage. It is the journal repairing an unclean mount, which is exactly what a journalling filesystem is for. The script reports it as context, not as a fault.
4. Scheduled jobs that survive an outage¶
cron does not run jobs it missed while the machine was off. An outage from 02:00 to 08:00 would mean that night's backup simply never happened — no error, no log line, no alert. A silent gap in the one system whose entire job is not having gaps.
All three jobs moved from the user crontab to systemd timers with Persistent=true, which records each run and fires a missed one shortly after the next boot.
| Timer | Schedule | Persistent |
Runs |
|---|---|---|---|
z2mini-backup.timer |
daily 03:00 | yes | backup-files.sh |
z2mini-backup-mount-check.timer |
hourly | yes | check-backup-mount.sh |
z2mini-token-age-check.timer |
Mondays 09:00 | yes | tailscale-token-age-check.sh |
z2mini-heartbeat.timer |
every 5 min | no | heartbeat.sh |
The heartbeat is deliberately not persistent. A missed heartbeat is the alarm itself — replaying it after the fact would be worse than useless, since it would tell the monitor everything was fine during an outage.
The old cron lines are commented out in place rather than deleted, prefixed # migrated to systemd timer 2026-09-09:, so crontab -l still shows the history.
Checking on them¶
systemctl list-timers 'z2mini-*'
systemctl status z2mini-backup.service
journalctl -u z2mini-backup.service -n 50
sudo systemctl start z2mini-backup.service
Unit files live in /etc/systemd/system/z2mini-*.service and z2mini-*.timer.
Verifying the whole chain¶
After any power event, these four commands tell you everything:
uptime -s
systemctl list-timers 'z2mini-*'
~/scripts/post-crash-check.sh --force
docker ps -q | wc -l
Expected: a boot time matching when power returned, no skipped timers, an all-clear email, and 19 containers.
What's still open¶
- No UPS. A short brownout still takes the machine down hard. Every unclean shutdown is a roll of the dice on database corruption — so far the dice have come up clean.
- The BIOS setting is manual. It cannot be scripted durably, so if the board is ever reset to defaults or replaced, it silently reverts. Re-check it after any firmware update.
- The heartbeat depends on the home internet. An ISP outage with the Z2 still running will page you as though the machine died. That is the correct failure direction — a false alarm beats silence — but expect it.
Related¶
- 05-backups.md — what the nightly backup covers
- 07-email-relay.md — the msmtp relay these alerts send through
- 08-recovery.md — Scenario 9 covers recovering from a power loss
- 09-maintenance.md — why you never
docker stopbefore a shutdown