Skip to content
Go back

NUT: Network UPS Tools for Home Labs That Don't Want to Crash

By SumGuy 10 min read
NUT: Network UPS Tools for Home Labs That Don't Want to Crash

Your UPS Is Lying to You (By Omission)

It’s 2 AM. A storm rolls through. Your UPS beeps twice, holds for eight minutes, then cuts out when the battery finally gives up. Your servers hard-power off. You wake up the next morning, coffee in hand, and discover your ZFS pool is in a degraded state because one of the drives was mid-write when the lights went out.

The UPS did exactly what it was supposed to: buy time. The problem? Nothing was watching it. There was no daemon, no notification, no countdown — just silence until the battery ran dry and physics took over.

That’s the gap NUT fills.

Network UPS Tools (NUT) is a decades-old, battle-tested open source project that talks to your UPS over USB or serial, exposes every stat (battery charge, runtime remaining, load percentage, input voltage, temperature) over the network, and — crucially — tells your hosts to shut down gracefully before the battery dies. One NUT server. Multiple clients. Zero corrupt filesystems.


The Architecture: Three Daemons, One Job

NUT splits the work cleanly:

The clean architecture means your NUT server can be your Proxmox host, a Pi, a cheap VM — whatever has a USB cable plugged into the UPS. Everything else just calls home over TCP.


Step 1: Find Your Driver

Don’t guess. Run discovery:

Terminal window
sudo apt install nut nut-client
sudo nut-scanner -U

nut-scanner probes USB for UPS devices and spits out a ready-to-paste config block. If it finds your APC Back-UPS, you’ll see something like:

[apc-backups]
driver = usbhid-ups
port = auto
desc = "APC Back-UPS 1500"

usbhid-ups handles the overwhelming majority of modern USB UPSes. The old apcupsd-era apcsmart driver is for serial-connected APC units with the smart cable — you probably don’t have one. If nut-scanner returns nothing, check lsusb to confirm the UPS is even seen by the OS, then look up your model in the NUT hardware compatibility list.

One gotcha: cheap “smart” UPSes that are actually dumb offline UPSes. They’ll enumerate as USB HID devices, the driver will load, but you’ll get nonsense readings — 99% battery charge forever, no runtime estimate. NUT can’t fix the hardware. A manufacturer: Generic in the output is your early warning sign.


Step 2: Configure the Server

Three config files. All live in /etc/nut/.

ups.conf — Define the UPS

/etc/nut/ups.conf
[myups]
driver = usbhid-ups
port = auto
desc = "CyberPower CP1500PFCLCD"
pollinterval = 2

The name in brackets (myups) becomes the identifier you reference everywhere else. Keep it short.

upsd.conf — Network Binding

By default, upsd only listens on localhost. If you want client machines to connect, open it up:

/etc/nut/upsd.conf
LISTEN 0.0.0.0 3493
MAXAGE 15

MAXAGE is how stale a data reading can get (in seconds) before upsd considers the connection to the driver dead. Default is 15. Don’t crank it high — if the driver crashes, you want to know fast.

upsd.users — Auth for Clients

/etc/nut/upsd.users
[upsmon]
password = changeme_please
upsmon master
[readonly]
password = readonlypw
actions = SET
instcmds = ALL
upsmon slave

master means this host initiates the final shutdown. slave means it shuts down first, then waits for master to finish. On a single-host setup, you’re always master.

nut.conf — Mode

/etc/nut/nut.conf
MODE=standalone

Options: standalone (server + client on same box), netserver (server only, clients elsewhere), netclient (client only). If you’re running a dedicated NUT server box, use netserver. If everything’s on one machine, use standalone.

Start It Up

Terminal window
sudo systemctl enable nut-server nut-monitor
sudo systemctl start nut-server nut-monitor
sudo upsc myups@localhost

That last command should dump a wall of metrics. If you see battery.charge: 100 and ups.status: OL (online), you’re talking to the UPS.


Step 3: Configure the Clients (upsmon.conf)

On the NUT server itself, and on every other host you want to protect:

/etc/nut/upsmon.conf
MONITOR [email protected]:3493 1 upsmon changeme_please master
MINSUPPLIES 1
SHUTDOWNCMD "/sbin/shutdown -h now"
NOTIFYCMD /usr/sbin/upssched
POLLFREQ 5
POLLFREQALERT 5
HOSTSYNC 15
DEADTIME 15
POWERDOWNFLAG /etc/killpower
NOTIFYMSG ONLINE "UPS %s: back on line power"
NOTIFYMSG ONBATT "UPS %s: on battery"
NOTIFYMSG LOWBATT "UPS %s: battery is low"
NOTIFYMSG COMMBAD "UPS %s: lost contact"
NOTIFYFLAG ONLINE SYSLOG+WALL
NOTIFYFLAG ONBATT SYSLOG+WALL+EXEC
NOTIFYFLAG LOWBATT SYSLOG+WALL+EXEC
NOTIFYFLAG COMMBAD SYSLOG+WALL

The MONITOR line format is upsname@host:port numpower username password role.

On slave machines (nodes that should shut down before the server), change master to slave. Slaves shut down first so they don’t get stranded waiting for a share from a host that’s already gone dark.

SHUTDOWNCMD is exactly what it sounds like. On Proxmox, you might want something fancier that gracefully migrates VMs first — more on that in a moment.


Docker Compose: NUT Server in a Container

If you want to run the NUT server as a container (useful on systems where you’d rather not install system daemons), here’s a working setup:

Full example: Clone the working files at github.com/KingPin/sumguy-examples/self-hosting/nut-ups-power-monitoring/

docker-compose.yml
services:
nut-upsd:
image: instantlinux/nut-upsd:latest
container_name: nut-upsd
restart: unless-stopped
ports:
- "3493:3493"
devices:
- /dev/bus/usb:/dev/bus/usb
environment:
- API_USER=upsmon
- API_PASSWORD=changeme_please
- DESCRIPTION=My Home Lab UPS
- DRIVER=usbhid-ups
- NAME=myups
- PORT=auto
- SERIAL=
volumes:
- nut-data:/etc/nut
volumes:
nut-data:

The devices passthrough is the critical piece — without it, the container can’t see the USB UPS. Run lsusb on the host first to confirm the device is there. If you’re on Proxmox and passing USB through to an LXC, you’ll need to add the USB device to the LXC config instead.

The container handles server duties. Install nut-client natively on each host that needs to respond to shutdown signals — upsmon needs to run as a proper system daemon to actually trigger a shutdown.


Integrations: Where It Gets Fun

Home Assistant

HA has a built-in NUT integration (Settings → Devices & Services → Add Integration → NUT). Point it at your NUT server’s IP and port. You’ll get battery charge, runtime, load, input/output voltage all as sensors. Wire them into an automation if you want a Slack message or push notification when the UPS kicks in — because your phone staying silent during a power outage is how you sleep through a 6-hour battery drain.

Prometheus + Grafana

The nut-exporter Prometheus exporter (by DRuggeri) scrapes upsc output and exposes it on :9995/metrics. Add it to your Compose stack:

docker-compose.yml
nut-exporter:
image: druggeri/nut_exporter:latest
container_name: nut-exporter
restart: unless-stopped
ports:
- "9995:9995"
environment:
- NUT_EXPORTER_SERVER=nut-upsd
- NUT_EXPORTER_USERNAME=readonly
- NUT_EXPORTER_PASSWORD=readonlypw

Then scrape it in your Prometheus config and build a dashboard. Watching battery runtime trend down over years tells you when it’s time to replace the cells — usually around the 3-year mark for VRLA batteries, though the cheap ones start lying to you earlier.

Proxmox UPS Integration

Proxmox has native UPS support in the Datacenter → UPS section, or you can run upsmon directly on the PVE host. The smarter approach is a custom SHUTDOWNCMD that gracefully shuts down VMs before the host goes down:

/usr/local/bin/proxmox-ups-shutdown.sh
#!/bin/bash
for vmid in $(qm list | awk 'NR>1 {print $1}'); do
qm shutdown $vmid --timeout 60
done
sleep 30
/sbin/shutdown -h now

Point SHUTDOWNCMD at that script. Your VMs get 60 seconds to flush writes, then the host shuts clean. Adjust the timeout based on how long your battery realistically lasts past the LOWBATT threshold.


NUT vs. apcupsd

If you’ve been around long enough, you know apcupsd. It’s been handling APC UPS monitoring since the mid-90s and it works fine — if you have APC hardware and only one host.

Here’s the honest comparison:

NUTapcupsd
Vendor supportAPC, CyberPower, Eaton, Tripplite, ~150 moreAPC only
Multi-hostNative (upsd + upsmon)Limited (apcupsd network mode is clunky)
Driver modelPluggable, maintained upstreamMonolithic
Home AssistantNative integrationCommunity workaround
Config complexityHigherLower
CommunityActiveQuiet but stable

If you bought a CyberPower or Eaton because the APC was $40 more at Best Buy, NUT is your only real option anyway. For pure APC shops, apcupsd gets the job done with less ceremony. But for a heterogeneous home lab, NUT wins by default.


The Shutdown Sequence: Don’t Wing This

The whole point of this exercise is a clean shutdown when power fails. Map it out explicitly:

  1. UPS goes on battery → upsd detects state change within POLLINTERVAL seconds
  2. upsmon on all hosts receives OB (on battery) notification
  3. Battery drains to threshold → upsd reports LB (low battery)
  4. Slave hosts receive OB LB → execute SHUTDOWNCMD immediately
  5. Master waits for slaves to acknowledge shutdown (up to HOSTSYNC seconds)
  6. Master executes SHUTDOWNCMD
  7. NUT sends poweroff command to UPS (if supported) so it cuts power cleanly and can restart when utility power returns

Test this before you need it. Seriously. Pull the plug during a maintenance window, watch the logs, confirm every host shuts down in the right order. A UPS shutdown sequence you’ve never tested is a false sense of security wearing a power brick costume.

Terminal window
# Watch upsmon in real time
sudo journalctl -u nut-monitor -f
# Manually check UPS state
upsc myups@localhost ups.status
upsc myups@localhost battery.runtime
upsc myups@localhost battery.charge

The battery.runtime value is your countdown clock. Set LOWBATT threshold high enough that your slowest host can complete a clean shutdown within that window. Default is usually 300 seconds — if your Proxmox host with 20 VMs needs 4 minutes to gracefully shut down, that’s cutting it close.


The Bottom Line

A UPS without software watching it is just an expensive extension cord with a battery. NUT turns it into an actual protection system — one that coordinates across your whole lab, exposes metrics, integrates with HA and Grafana, and handles the thing you actually care about: making sure your data isn’t mid-write when the lights go out.

Set it up once. Test it. Then forget about it until you get the notification at 2 AM that power just blipped and everything handled it gracefully while you stayed in bed.

That’s the goal. Boring infrastructure that works.


Share this post on:

Send a Webmention

Written about this post on your own site? Send a webmention and it'll show up above once verified.


Next Post
iperf3 + nload: Network Diagnosis

Discussion

Powered by Garrul . Sign in with GitHub or Google, or post anonymously.

Related Posts