← All Guides
intermediate

UPS Monitoring with NUT: Make Your Homelab Shut Itself Down

Set up Network UPS Tools on Proxmox so your homelab shuts down cleanly on battery. USB and SNMP drivers, upsmon, shutdown timing, secondary nodes, and how to test it without faking it.

Budget Homelab ·
hardwareupshow-toproxmox

This guide contains no paid affiliate links. It is configuration, not shopping. If you are still choosing a unit, the UPS buying guide covers sizing and picks.

A UPS that your server cannot talk to is a surge protector with a heavy battery in it. It will ride out flickers and brownouts, which is genuinely worth something, but when the power stays out it will run the battery flat and then drop the machine exactly as hard as the outage would have. The piece that turns a battery into actual protection is software, and on Linux that software is Network UPS Tools. This guide wires NUT into a Proxmox host, which pairs with the rest of the post-install checklist as the step most people skip.

The plan is straightforward: get the UPS talking to one machine, decide how long you are willing to sit on battery before giving up, make the host shut itself down inside that window, and then prove it works by pulling the plug. If more than one machine shares the UPS, the same install feeds the others over the network. Power draw matters here too, because runtime is a function of load, and the homelab power usage numbers are what tell you how much time you actually have.

Every address below is a placeholder. Substitute your own.

Quick Answer

# install on the machine with the UPS cable
sudo apt install nut-server nut-client

# find the UPS and get a ready-made config block
sudo nut-scanner -U

# after editing /etc/nut/ups.conf, nut.conf, upsd.users, upsmon.conf:
sudo systemctl restart nut-server nut-monitor

# confirm it is reading the UPS
upsc homelab-ups@localhost

That is the whole shape of it. The rest of this guide is the difference between a config that reports battery percentage and a config that saves your filesystem at 2am.

What You Need First

Step 1: Connect and Identify the UPS

Plug the USB cable in, then check the kernel saw it:

lsusb

Look for the vendor name. Most consumer units appear as a generic HID device from APC, CyberPower, Eaton, or Tripp Lite.

Install NUT:

sudo apt update
sudo apt install nut-server nut-client

Then let NUT work out the driver for you:

sudo nut-scanner -U

nut-scanner prints a config block ready to paste, including the correct driver name. This is worth doing even if you think you know the driver, because getting it wrong is the single most common reason a NUT setup reports a UPS that never changes status.

The drivers you will actually encounter:

DriverTypical hardware
usbhid-upsMost modern USB units: APC Back-UPS, CyberPower, Eaton
blazer_usbCheaper generic and rebadged units
nutdrv_qxMegatec/Q1 protocol units, the successor to blazer_usb
snmp-upsAnything with a network management card
dummy-upsTesting and relaying, no real hardware

Step 2: Define the UPS

Edit /etc/nut/ups.conf:

[homelab-ups]
    driver = usbhid-ups
    port = auto
    desc = "Homelab UPS"
    pollinterval = 5

For a network-managed unit instead:

[homelab-ups]
    driver = snmp-ups
    port = 192.168.1.20
    community = public
    snmp_version = v1
    desc = "Rack UPS via NMC"

Test the driver before touching any services:

sudo upsdrvctl start

A clean attach looks unremarkable, which is the point. If it fails, run it verbose and read the actual error rather than guessing:

sudo upsdrvctl -D start

Permission errors on USB are the other common failure. The NUT packages ship udev rules that hand the device to the nut user, but those rules apply on device connection, so unplugging the USB cable and plugging it back in after install fixes a surprising number of cases.

Step 3: Set the Mode and Start upsd

/etc/nut/nut.conf decides what this machine is:

MODE=standalone

Use standalone when one machine owns the UPS and nothing else needs to hear about it. Use netserver when other machines on the UPS will monitor it over the network. Those are the only two you need; netclient belongs on the other machines, covered later.

Start the server:

sudo systemctl enable --now nut-server
upsc homelab-ups@localhost

You should get a block of variables:

battery.charge: 100
battery.runtime: 2880
ups.load: 12
ups.status: OL

Those four lines are the ones that matter. ups.status is the state machine everything else keys off:

StatusMeaning
OLOnline, running on mains
OBOn battery
LBLow battery, as defined by the UPS firmware
OL CHRGOn mains, recharging
FSDForced shutdown, someone or something called it

Step 4: Create upsd Users

/etc/nut/upsd.users controls who may talk to the server. Create the monitoring account:

[monuser]
    password = change-this-to-something-random
    upsmon primary

If you also want to run instant commands like a self-test, add a separate admin account rather than granting the monitor extra rights:

[upsadmin]
    password = a-different-random-string
    actions = SET
    instcmds = ALL

Older documentation says master and slave where current NUT says primary and secondary. Recent releases accept both, but write the current keywords; you will not have to think about it again.

Lock the file down and restart:

sudo chown root:nut /etc/nut/upsd.users
sudo chmod 640 /etc/nut/upsd.users
sudo systemctl restart nut-server

Step 5: Configure upsmon

This is the file that does the work. In /etc/nut/upsmon.conf:

MONITOR homelab-ups@localhost 1 monuser change-this-to-something-random primary

MINSUPPLIES 1
SHUTDOWNCMD "/sbin/shutdown -h +0"
POLLFREQ 5
POLLFREQALERT 5
HOSTSYNC 15
DEADTIME 15
POWERDOWNFLAG /etc/killpower

FINALDELAY 5

The parts that decide behaviour:

Start it:

sudo systemctl enable --now nut-monitor

Step 6: Decide When to Give Up

By default, upsmon shuts down when the UPS reports OB LB, meaning on battery and low. That is one reasonable policy and it is the simplest, but it hands the decision to the UPS firmware, and on budget units the low-battery threshold can fire with very little runtime left.

The alternative is to shut down after a fixed time on battery, which you choose. That is upssched, driven from upsmon.conf:

NOTIFYCMD /usr/sbin/upssched
NOTIFYFLAG ONBATT SYSLOG+EXEC
NOTIFYFLAG ONLINE SYSLOG+EXEC
NOTIFYFLAG LOWBATT SYSLOG+EXEC

Then in /etc/nut/upssched.conf:

CMDSCRIPT /etc/nut/upssched-cmd
PIPEFN /run/nut/upssched.pipe
LOCKFN /run/nut/upssched.lock

AT ONBATT * START-TIMER onbatt-shutdown 300
AT ONLINE * CANCEL-TIMER onbatt-shutdown
AT LOWBATT * EXECUTE onbatt-shutdown

That starts a five-minute timer when the power drops, cancels it if mains returns, and fires immediately if the battery goes low first. The handler in /etc/nut/upssched-cmd:

#!/bin/sh
case $1 in
    onbatt-shutdown)
        logger -t upssched-cmd "On battery too long, shutting down"
        /usr/sbin/upsmon -c fsd
        ;;
esac

Make it executable:

sudo chmod +x /etc/nut/upssched-cmd

Five minutes is a starting point, not a recommendation. The right number is: how long does a clean shutdown of everything on this host actually take, plus a margin, and does the UPS have that much runtime at your measured load. Time your own shutdown and work backwards. A host running a dozen guests needs a longer window than a single mini PC, and the battery.runtime value from upsc tells you what you have to spend.

Step 7: Secondary Nodes

If a second machine is plugged into the same UPS, it needs to know when the power is going. It does not need its own USB connection.

On the primary, switch /etc/nut/nut.conf to:

MODE=netserver

And let upsd listen on the LAN in /etc/nut/upsd.conf:

LISTEN 127.0.0.1 3493
LISTEN 192.168.1.10 3493

Add a secondary account to upsd.users on the primary:

[secondaryuser]
    password = another-random-string
    upsmon secondary

On the second machine, install nut-client only, set MODE=netclient, and point it at the primary:

MONITOR homelab-ups@192.168.1.10 1 secondaryuser another-random-string secondary

Restart nut-monitor there and confirm it can read the UPS:

upsc homelab-ups@192.168.1.10

Port 3493 carries credentials in a form you do not want loose on a flat network. Firewall it to the specific hosts that need it rather than the whole subnet.

Secondaries shut down first. The primary waits HOSTSYNC seconds for them to disconnect before halting itself, which is what keeps the machine holding the UPS connection alive long enough to be useful.

Step 8: Close the Power-Cycle Loop

This is the step most guides omit, and it is the one that decides whether an outage is self-healing or a morning of walking around pressing power buttons.

When upsmon shuts the host down, the UPS is still supplying power. If nothing tells it otherwise it will keep doing that until the battery is flat, and when mains comes back the UPS has nothing left to start with. The server stays off.

The fix is the POWERDOWNFLAG from Step 5. When upsmon initiates the shutdown it writes that file; late in the halt sequence the system checks for it and, if present, calls upsdrvctl shutdown to tell the UPS to cut its own output after a delay. Mains returns, the UPS powers up, the server powers up with it.

The Debian and Proxmox packages wire this up for you. Verify rather than assume:

systemctl is-enabled nut-driver-enumerator.service
ls -l /lib/systemd/system-shutdown/nutshutdown 2>/dev/null

The delays themselves live in ups.conf on units that support them:

[homelab-ups]
    driver = usbhid-ups
    port = auto
    offdelay = 60
    ondelay = 120

offdelay is how long the UPS waits before cutting output, which must be longer than the host takes to finish halting. ondelay is how long it waits before restoring power, which should be longer than offdelay so the two never race.

Make sure your BIOS is set to power on after AC loss, or none of this matters. On most mini PCs that setting is called “Restore on AC Power Loss” or “After Power Failure,” and the default is frequently “stay off.”

Step 9: Get Told About It

A shutdown you find out about the next morning is a worse outcome than one you watch happen. upsmon.conf can run a command on any state change:

NOTIFYFLAG ONBATT SYSLOG+EXEC
NOTIFYFLAG ONLINE SYSLOG+EXEC
NOTIFYFLAG LOWBATT SYSLOG+EXEC
NOTIFYFLAG SHUTDOWN SYSLOG+EXEC

Point NOTIFYCMD at a script that pushes to whatever you already run. If that is self-hosted push, the ntfy guide covers the receiving end; if it is mail, the homelab email notifications setup applies unchanged. The important detail is that the notification path must not depend on the homelab surviving, because the entire scenario is the homelab not surviving. A push service or an external mail relay clears that bar. A self-hosted mail server on the box that is shutting down does not.

Step 10: Test It Properly

Three levels, in order.

Forced shutdown drill. This exercises the real code path without touching the hardware:

sudo upsmon -c fsd

The host should shut down now. On Proxmox, watch that guests stop first rather than getting cut off. Boot it back up and read the logs:

journalctl -u nut-monitor --since "30 minutes ago"

Battery self-test. Confirms the UPS reports state changes correctly:

upscmd -u upsadmin -p a-different-random-string homelab-ups test.battery.start.quick
upsc homelab-ups@localhost ups.status

Pull the plug. Nothing else proves it. Pick a quiet evening, unplug the UPS from the wall, and watch the whole sequence: status flips to OB, notification arrives, timer runs, guests stop, host halts, UPS cuts output. Plug it back in and confirm everything comes back without you touching a keyboard.

Do this once a year. Batteries die quietly, and a UPS with a dead battery reports the same reassuring OL it always did right up until the moment it matters. battery.runtime dropping over time is the early warning, which is worth graphing if you already run monitoring.

What Usually Goes Wrong

SymptomCause
ups.status never leaves OLWrong driver, or a data cable that is charge-only
Driver not connected from upscupsd started before the driver attached; restart nut-server
Permission denied on the USB deviceudev rules applied after install; reconnect the cable
Host halts but never powers back onPOWERDOWNFLAG path missing, or BIOS set to stay off after AC loss
Secondary never shuts downupsd not listening on the LAN address, or port 3493 firewalled
Guests get cut off mid-writeShutdown window shorter than the guests need; raise the timer or the per-guest timeout

What This Actually Buys You

A working NUT setup does not extend your runtime by a single second. What it does is convert an uncontrolled power loss into a controlled one, which is the difference between a filesystem that mounts on the next boot and one that needs a recovery session. On a host running databases, ZFS, or anything mid-write, that is the whole value proposition of owning the UPS in the first place.

The configuration above is maybe an hour of work. The test at the end is the part that counts, and it is the part that gets skipped, which is why so many homelabs own a UPS that has never once done its job.