NUT shutdown on a timer: upssched, secondaries first, and HOSTSYNC explained
Network UPS Tools can shut your homelab down after N minutes on battery instead of waiting for "low battery". How a NUT shutdown runs, a Debian secondary client, TrueNAS as a client, an upssched timer with its script and tmpfiles rule, and the timing settings that matter.

A NUT server that shuts itself down perfectly is not enough. If the NAS was never told, or the Docker host in the corner was never told, the outage still ends in a crash. And by default NUT waits for the UPS to report low battery, a threshold the UPS picks and an estimate that gets optimistic as the battery ages. This post connects the clients, explains the order NUT shuts things down in, and replaces "wait for low battery" with a timed shutdown that keeps most of the battery in reserve.
The examples assume the UPS's USB cable is on a Proxmox host running the NUT server (upsd) and the primary upsmon, with the UPS named myups at 10.0.10.5.
How a NUT shutdown actually runs
When the UPS goes critical (on battery and low battery, or the forced-shutdown flag is set), this is the sequence:
- The primary
upsmonnotices and sets the FSD (forced shutdown) flag on the UPS inupsd. - Every secondary
upsmonsees FSD on its next poll, waitsFINALDELAYseconds and runs its ownSHUTDOWNCMD. As it shuts down, it disconnects fromupsd. - The primary waits until all secondaries have disconnected, or until
HOSTSYNCseconds have passed, whichever comes first. - The primary shuts its own machine down, creating the
POWERDOWNFLAGfile on the way. - Late in the primary's shutdown, that flag tells the driver to command the UPS to cut its output after
offdelayseconds.
That ordering is built in: secondaries always go before the primary. Put the NAS, the other servers and any VMs that run elsewhere on secondaries, and keep the primary on the machine with the UPS cable.
A Debian client
On another Debian or Ubuntu machine that should shut down with the UPS, install only the client part:
apt install nut-client
Set the mode in /etc/nut/nut.conf:
MODE=netclient
Then point upsmon at the server with a secondary user you created in upsd.users:
# /etc/nut/upsmon.conf
MONITOR myups@10.0.10.5 1 upsmon_remote CHANGE-ME-long-random secondary
MINSUPPLIES 1
SHUTDOWNCMD "/sbin/shutdown -h +0"
NOTIFYFLAG ONBATT SYSLOG+WALL
NOTIFYFLAG ONLINE SYSLOG+WALL
NOTIFYFLAG FSD SYSLOG+WALL
NOTIFYFLAG COMMBAD SYSLOG+WALL
Lock the file down, start the monitor and check that the client can read the UPS:
chown root:nut /etc/nut/upsmon.conf
chmod 0640 /etc/nut/upsmon.conf
systemctl enable --now nut-monitor.service
upsc myups@10.0.10.5 ups.status
A secondary never has a POWERDOWNFLAG: only the primary tells the UPS to cut power. Treat the secondary password as a low-value credential that only lets a client follow the UPS, and never reuse the admin password on a client.
TrueNAS as a client
TrueNAS has a built-in UPS service that is NUT underneath. In TrueNAS 25.10 it is under System > Services > UPS (the pencil icon). Set Identifier to myups, UPS Mode to Slave (TrueNAS still uses the old word for a secondary), Remote Hostname and Remote Port to the server and 3493, and the monitor user and password. Save, turn the service on and set it to start automatically. Menu names have moved between TrueNAS releases, so check the UPS service page in the docs for your version if the screen looks different.
The NAS is usually the machine whose clean shutdown matters most and takes longest. Time it. By default a secondary upsmon logs off upsd as soon as it has started its shutdown, so HOSTSYNC only covers that hand-off, not the whole NAS shutdown. What protects the rest of it is the time the primary's own shutdown takes plus the UPS's offdelay; make sure those together are longer than the NAS needs.
Shutdown on a timer, not on low battery
The alternative to the UPS's low-battery threshold is to decide for yourself: if the UPS has been on battery for N minutes, the outage is real, so shut everything down while the battery is still mostly full. NUT does this with upssched, a timer helper that upsmon calls on events.
On the primary, tell upsmon to call upssched for the on-battery and on-line events:
# add to /etc/nut/upsmon.conf on the primary
NOTIFYCMD /sbin/upssched
NOTIFYFLAG ONBATT SYSLOG+WALL+EXEC
NOTIFYFLAG ONLINE SYSLOG+WALL+EXEC
Check the real path with command -v upssched and use that. Then configure the timer. The stock upssched.conf ships with PIPEFN disabled on purpose, so you set it deliberately:
# /etc/nut/upssched.conf
CMDSCRIPT /etc/nut/upssched-cmd
PIPEFN /run/nut/upssched/upssched.pipe
LOCKFN /run/nut/upssched/upssched.lock
AT ONBATT * START-TIMER onbatt 180
AT ONLINE * CANCEL-TIMER onbatt
START-TIMER onbatt 180 starts a three-minute timer when the UPS goes on battery, and CANCEL-TIMER stops it if power comes back first. If the timer runs out, upssched runs CMDSCRIPT with the timer name as its argument. Pick the number from your own runtime at your real load, not from the box.
The script:
#!/bin/sh
# /etc/nut/upssched-cmd
case "$1" in
onbatt)
logger -t upssched-cmd "On battery for too long, forcing shutdown"
/sbin/upsmon -c fsd
;;
*)
logger -t upssched-cmd "Unrecognized timer: $1"
;;
esac
upsmon -c fsd sets the forced-shutdown flag from the primary, which starts the normal ordered shutdown: secondaries first, then the primary. Create the directory for the pipe and lock file, owned by the user upsmon runs as (nut on Debian), make the script executable, and restart:
install -d -o nut -g nut -m 0750 /run/nut/upssched
chmod 0750 /etc/nut/upssched-cmd
chown root:nut /etc/nut/upssched-cmd /etc/nut/upssched.conf
systemctl restart nut-monitor.service
/run is cleared at every boot, so the directory has to be recreated each time. A one-line tmpfiles rule does it:
# /etc/tmpfiles.d/nut-upssched.conf
d /run/nut/upssched 0750 nut nut -
Do not test the timer by pulling the plug before the script works. Run the script by hand first, with a short timer, at a time when a real shutdown costs nothing.
The timing settings
| Setting | Where | Default | Change it when |
|---|---|---|---|
HOSTSYNC | Primary upsmon.conf | 15 s | Your slowest secondary needs longer to disconnect |
FINALDELAY | Every upsmon.conf | 5 s | You want more warning before SHUTDOWNCMD; keep it below the primary's HOSTSYNC |
DEADTIME | Every upsmon.conf | 15 s | Rarely; keep it a multiple of POLLFREQ |
POLLFREQ | Every upsmon.conf | 5 s | Rarely |
START-TIMER | Primary upssched.conf | none | Your own choice from your measured runtime |
Home Assistant, for eyes only
Home Assistant's Network UPS Tools integration does not shut anything down; it reads the NUT server so you can see the UPS on a dashboard and get a phone notification when it goes on battery. Give it its own user with no upsmon, actions or instcmds lines in upsd.users, so it can read status but change nothing, and reload upsd with systemctl reload nut-server.
Checklist
- Every machine that needs a clean shutdown runs a secondary
upsmonor its own UPS client. upsc myups@<server>works from each client.- TrueNAS's UPS service is a secondary pointed at the server, enabled at boot.
- The primary's shutdown time plus
offdelayis longer than the NAS needs. - The
upsschedtimer, script and tmpfiles rule are in place, and the script was tested by hand.
