Free weekly email: the week's new posts, videos and one tip from the books, every Sunday. Get it free →
Homelab the Right Way

Home › Posts

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.

· 5 min read · upsnutnetwork ups toolsproxmoxtruenashomelab

NUT shutdown on a timer: upssched, secondaries first, and HOSTSYNC explained

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:

  1. The primary upsmon notices and sets the FSD (forced shutdown) flag on the UPS in upsd.
  2. Every secondary upsmon sees FSD on its next poll, waits FINALDELAY seconds and runs its own SHUTDOWNCMD. As it shuts down, it disconnects from upsd.
  3. The primary waits until all secondaries have disconnected, or until HOSTSYNC seconds have passed, whichever comes first.
  4. The primary shuts its own machine down, creating the POWERDOWNFLAG file on the way.
  5. Late in the primary's shutdown, that flag tells the driver to command the UPS to cut its output after offdelay seconds.

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

SettingWhereDefaultChange it when
HOSTSYNCPrimary upsmon.conf15 sYour slowest secondary needs longer to disconnect
FINALDELAYEvery upsmon.conf5 sYou want more warning before SHUTDOWNCMD; keep it below the primary's HOSTSYNC
DEADTIMEEvery upsmon.conf15 sRarely; keep it a multiple of POLLFREQ
POLLFREQEvery upsmon.conf5 sRarely
START-TIMERPrimary upssched.confnoneYour 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 upsmon or 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 offdelay is longer than the NAS needs.
  • The upssched timer, script and tmpfiles rule are in place, and the script was tested by hand.

More articles