Setting up NUT UPS Software on Linux (a journey of discovery)
Preamble
I've written about this UPS software numerous times. The coverage was normally a stage in a larger guide. I was asked by a friend to help him setup NUT (Network UPS Tools) on his Linux box and decided to feed my notes into an AI, prompting it to update them and make improvements.
NUT is an open-source solution distributed under the GNU General Public License (GPL). It is manufacture neutral and is heading towards its 30th anniversary. Another important observations is that it is able to support a wide array of devices spanning the evolution of the technology over the years. To this one needs to add the fact that there is no standards body to ensure that every brand follows a fixed API that had evolved with oversight over time.
The relevance of this information to the narrative is that the persons who support this project are volunteers who dedicate their own time to maintaining the project and do not have access to every UPS ever produced.
This means that if the UPS you are monitoring with NUT is a unit that is correctly detected by NUT scanner the process is straightforward. If it is not you have to put in a bit of elbow grease or work with the NUT volunteers to find the correct driver for your UPS.
The official site of NUT is https://networkupstools.org/.
The UPS I was working with is an older model. I’ve decided to document this journey of discovery in the hope that it guides you on what to look for—and how to resolve issues—when setting up NUT with your own hardware.
All the output is from the UPS I wanted to have monitored by NUT.
Below is the updated guide.
Parameters used in this document
This tutorial is based around a specific use case. Your values will be different. You may want to take this document and perform a find & replace to reflect your environment. Such a change will make it easier to copy and paste scripts across to your setup.
| Parameter | value | Command / Comments |
|---|---|---|
| vendor_id | 0665 | lsusb and nut-scanner |
| productid | 5161 | lsusb and nut-scanner |
| serial | ****0826 | nut-scanner |
| port | auto | nut-scanner |
| driver | nutdrv_qx | nut-scanner |
| driver | blazer_usb | nut-scanner |
| ups name | pve_ups | multiple config files |
| upsmon | monitoring account / multiple config files | |
| upsmon | upsmon_pass | multiple config files |
| upsadmin | admin account / multiple config files | |
| updasmin | updasmin_pass | admin password |
| email@addr.com | email address that will receive notifications | |
| smtpemail@addr.com | smtp sending email address | |
| smtpemail_pass | smtp app password |
Hardware and Goals
OS: Debian 14.2.0
UPS Model: Powerwalker VI 2200 IEC/UK
S/N: **** 0154
Shutdown conditions: if on-battery for 5 minutes, or battery charge ≤ 40%; allow 5 minutes for OS shutdown before UPS power cut-off.
Installing NUT
apt update
apt install nut nut-client nut-server -y
This installs the NUT daemons and utilities (including the upsdrvctl, upsd, upsmon, upsc, etc.).
Identifying the UPS and Driver
- Plug in the UPS and run
lsusbto find its USB IDs:
lsusb
The output shows e.g. Bus 001 Device 004: ID 0665:5161, where vendor_id=0665 and productid=5161.
Bus 001 Device 004: ID 0665:5161 Cypress Semiconductor USB to Serial
- Use
nut-scannerto identify the correct driver and settings:
nut-scanner -U
Output:
Scanning USB bus.
[nutdev1]
driver = "nutdrv_qx"
port = "auto"
vendorid = "0665"
productid = "5161"
product = "USB to Serial"
serial = "****0826"
vendor = "INNO TECH"
bus = "001"
device = "004"
busport = "011"
###NOTMATCHED-YET###bcdDevice = "0002"
Configuring the UPS (ups.conf)
Edit /etc/nut/ups.conf and add a section for the UPS using the values above.
nano /etc/nut/ups.conf
Add the following block (using the values from nut-scanner):
# /etc/nut/ups.conf
# Global Directives: Define maximum retries and polling intervals
maxretry = 3
pollinterval = 2
[pve_ups]
driver = nutdrv_qx
port = auto
vendorid = 0665
productid = 5161
desc = "INNO TECH USB UPS"
# Set low battery threshold to 40%
default.battery.charge.low = 40
override.battery.charge.low = 40
# serial = (if needed)
# Optional: override protocol if needed
# protocol = megatec
- maxretry: defines how many times NUT will try to start the UPS driver if the initial attempt fails.
- pollinterval: defines how frequently (in seconds) the driver polls the UPS for status updates.
- driver/port/vendorid/productid: as detected.
- desc: human-readable name.
- default.battery.charge.low: desired low-battery threshold (40%).
- override.battery.charge.low: When a UPSs default.battery.charge.low is read only we are not able to change it. In such a case we also set
override.battery.charge.low. This topic is further explored below.
Note: If the driver fails to start or can’t read the UPS and your UPS has protocol hints, try adding a protocol hint (the nutdrv_qx driver supports various protocols, e.g. protocol = megatec).
Test the driver:
upsdrvctl start
Output
Network UPS Tools - UPS driver controller 2.8.1
Network UPS Tools - Generic Q* USB/Serial driver 0.36 (2.8.1)
USB communication driver (libusb 1.0) 0.46
dstate_setflags: base variable (battery.charge.low) is immutable
Using protocol: Q1 0.08
Can't autodetect number of battery packs [-1/27.10]
Battery runtime will not be calculated (runtimecal not set)
Note the outputs discussed above:
- base variable (battery.charge.low) is immutable message;
- Battery runtime will not be calculated (runtimecal not set). If your UPS only reports the battery charge percentage (e.g., 80%) without telling the driver how many minutes that equates to. NUT can calculate this for you, but it needs a "map" to do the math. Since that map (the
runtimecalvariable) isn't in your config file, NUT is letting you know it won't be calculated.
Troubleshooting: Low Battery Threshold
Attempting to set battery.charge.low directly in the UPS causes an error (read-only). Instead, use the override parameter or the upsrw command. For example:
upsrw -s battery.charge.low=40 -u upsadmin -p upsadmin_pass pve_ups@localhost
NUT uses override.battery.charge.low as a software override of the UPS’s immutable built-in threshold. In other words, by default the UPS will signal “low battery” at its own hardware threshold, causing upsmon to shut down. Setting the override tells NUT to use the custom 40% level instead.
UPS parameters that can be changed
For a list of all the parameters that are read-write on a ups:
upsrw pve_ups@localhost
Verifying UPS Settings
List / Check values NUT sees:
upsc pve_ups@localhost
Output:
Init SSL without certificate database
battery.charge.low: 40
battery.voltage: 27.1
device.type: ups
driver.debug: 0
driver.flag.allow_killpower: 0
driver.name: nutdrv_qx
driver.parameter.default.battery.charge.low: 40
driver.parameter.override.battery.charge.low: 40
driver.parameter.pollfreq: 30
driver.parameter.pollinterval: 2
driver.parameter.port: auto
driver.parameter.productid: 5161
driver.parameter.synchronous: auto
driver.parameter.vendorid: 0665
driver.state: quiet
driver.version: 2.8.1
driver.version.data: Q1 0.08
driver.version.internal: 0.36
driver.version.usb: libusb-1.0.28 (API: 0x100010a)
input.frequency: 49.6
input.voltage: 235.2
input.voltage.fault: 235.2
output.voltage: 234.7
ups.beeper.status: enabled
ups.delay.shutdown: 30
ups.delay.start: 180
ups.load: 7
ups.productid: 5161
ups.status: OL
ups.type: offline / line interactive
ups.vendorid: 0665
Missing remaining charge
When inspecting the output I noticed that there was no charge parameter. If the driver could not read the UPS's remain charge it could not shutdown devices and the UPS when the remain charge was less than 40%.
I visited the NUT Hardware compatibility list page https://networkupstools.org/stable-hcl.html, selected the device type / model. The closest model I could find was "VI 2200 SH" and the driver was usbhid-ups.
I modified ups.conf and tried reloading the driver. I got am error message that the driver failed to start.
I noticed the blazer_usb driver and decided to try that.
Output:
Network UPS Tools - UPS driver controller 2.8.1
Network UPS Tools - Megatec/Q1 protocol USB driver 0.17 (2.8.1)
dstate_setflags: base variable (battery.charge.low) is immutable
Duplicate driver instance detected (PID file /run/nut/blazer_usb-pve_ups.pid exists)! Terminating other driver!
Please note that this driver is deprecated and will not receive
new development. If it works for managing your devices - fine,
but if you are running it to try setting up a new device, please
consider the newer nutdrv_qx instead, which should handle all 'Qx'
protocol variants for NUT. (Please also report if your device works
with this driver, but nutdrv_qx would not actually support it with
any subdriver!)
Supported UPS detected with megatec protocol
Vendor information unavailable
No values provided for battery high/low voltages in ups.conf
Using 'guesstimation' (low: 20.800000, high: 26.000000)!
Battery runtime will not be calculated (runtimecal not set)
Rerunning upsc pve_ups@localhost
Init SSL without certificate database
battery.charge: 100
battery.charge.low: 40
battery.voltage: 27.10
battery.voltage.high: 26.00
battery.voltage.low: 20.80
battery.voltage.nominal: 24.0
device.type: ups
driver.debug: 0
driver.flag.allow_killpower: 0
driver.name: blazer_usb
driver.parameter.default.battery.charge.low: 40
driver.parameter.override.battery.charge.low: 40
driver.parameter.pollinterval: 2
driver.parameter.port: auto
driver.parameter.productid: 5161
driver.parameter.synchronous: auto
driver.parameter.vendorid: 0665
driver.state: quiet
driver.version: 2.8.1
driver.version.internal: 0.17
driver.version.usb: libusb-1.0.28 (API: 0x100010a)
input.current.nominal: 9.0
input.frequency: 49.8
input.frequency.nominal: 50
input.voltage: 240.6
input.voltage.fault: 241.1
input.voltage.nominal: 230
output.voltage: 241.1
ups.beeper.status: enabled
ups.delay.shutdown: 30
ups.delay.start: 180
ups.load: 7
ups.productid: 5161
ups.status: OL
ups.type: offline / line interactive
ups.vendorid: 0665
While the battery.charge was showing I decided to follow the driver's advice and check whether I could get it to work with the nutdrv_qx specifying the protocol rather than leaving it on auto. Asking AI, it returned that "The q1 protocol is the foundational "Megatec" dialect. It is often the correct choice for Powerwalker units that are older or use a simplified firmware set."
While this config loaded it did not return the charge.
From https://networkupstools.org/docs/man/nutdrv_qx.html I got a list of all the protocols and decided to try each one in turn. q1 was the only protocol that worked but it did not output the charge.
I therefore decided to revert to the blazer_usb driver.
The updated version of /etc/nut/ups.conf:
# Global Directives: Define maximum start delay and polling intervals
maxretry = 3
pollinterval = 2
[pve_ups]
driver = blazer_usb
# driver = nutdrv_qx # q1 protocol does not return charge informaiton
# driver = usbhid-ups # Driver failed to start (exit status=1)
port = auto
vendorid = 0665
productid = 5161
desc = "INNO TECH USB UPS"
# Set low battery threshold to 40%
default.battery.charge.low = 40
override.battery.charge.low = 40
# Optional: override protocol if needed
# protocol = q1
# Comment out this line if your UPS allows you to write to you don't get a >
override.battery.charge.low = 40
NUT Operating Mode (nut.conf)
Set NUT to standalone mode (single UPS, single system):
nano /etc/nut/nut.conf
Set:
MODE=standalone
This instructs NUT to run as a single-system setup (one UPS, one computer), starting all three layers (driver, upsd, upsmon) together.
NUT Server Configuration (upsd.conf)
Edit /etc/nut/upsd.conf to restrict listening to localhost:
nano /etc/nut/upsd.conf
Add lines:
LISTEN 127.0.0.1 3493
# LISTEN ::1 3493 # uncomment if IPv6 is enabled
This makes the UPSD server accept connections only from the local machine.
Creating NUT Users (upsd.users)
Edit /etc/nut/upsd.users to define users for monitoring and administration:
[upsmon]
password = upsmon_pass # Change to a secure password
upsmon master # Grants monitoring (and master) privileges
[upsadmin]
password = upsadmin_pass # Change to a secure password
actions = SET
instcmds = ALL
- upsmon: monitoring user (with “master” flag for directly-connected UPS).
- upsadmin: administrative user allowed to issue SET and instant commands.
Secure the file permissions, since it contains plain-text passwords:
mod 640 /etc/nut/upsd.users
chown root:nut /etc/nut/upsd.users
This ensures only root or NUT group can read it.
Configuring upsmon (upsmon.conf)
The upsmon daemon monitors UPS status and initiates shutdown when needed. In /etc/nut/upsmon.conf, configure like this:
nano /etc/nut/upsmon.conf
MONITOR pve_ups@localhost 1 upsmon upsmon_pass master
HOSTSYNC 300 # Timeout for secondaries (not used here, set to 5 min)
MINSUPPLIES 1 # Minimum number of UPS that must be on-line
FINALDELAY 300 # Wait 300s after shutdown before UPS cuts power
SHUTDOWNCMD "/sbin/shutdown -h now"
NOTIFYCMD /etc/nut/notify.sh
# Notification flags: execute script and log on these events
NOTIFYFLAG ONLINE SYSLOG+WALL
NOTIFYFLAG ONBATT SYSLOG+WALL+EXEC
NOTIFYFLAG LOWBATT SYSLOG+WALL+EXEC
NOTIFYFLAG COMMOK SYSLOG+WALL
NOTIFYFLAG COMMBAD SYSLOG+WALL+EXEC
NOTIFYFLAG SHUTDOWN SYSLOG+WALL+EXEC
POLLFREQ 5
POLLFREQALERT 5
DEADTIME 15 # Seconds since lost communication before COMMBAD
POWERDOWNFLAG /etc/killpower
- The MONITOR line lists the UPS name, host, capacity (1 = master), user, password, and “master”.
- HOSTSYNC (300s) is used in multi-computer setups; here it can be set high to avoid premature exit.
- MINSUPPLIES 1 means shutdown if 1 (our only) UPS is on battery and low.
- FINALDELAY gives the OS up to 5 minutes to shut down gracefully before the UPS cuts power.
- SHUTDOWNCMD is the command to halt the machine.
- NOTIFYCMD points to our script for sending emails.
- NOTIFYFLAG lines specify which events trigger notifications (e.g. ONBATT = on battery).
- POLLFREQ/ALERT and DEADTIME control polling intervals and communication loss handling.
- POWERDOWNFLAG (with
/etc/killpower) can be used to send the UPS the “FSD” (forced shutdown) command after the OS halts.
Think of upsmon as the “safety officer” of NUT. It checks status regularly and triggers the shutdown sequence when conditions are met.
Email Notification Script
Create a script /etc/nut/notify.sh that upsmon will call on events:
nano /etc/nut/notify.sh
#!/bin/bash
# NUT UPS event notification script
# Called by upsmon with the event message as $1
RECIPIENT="email@addr.com" # <-- Change this
HOSTNAME=$(hostname -f)
SUBJECT="[UPS Alert] $HOSTNAME: $1"
echo "UPS Event on $HOSTNAME at $(date): $1" | mail -s "$SUBJECT" "$RECIPIENT"
This script uses the mail command to send alerts. For information on how to setup Postfix click here. If you have a different smtp solution you need to adjust the script.
Make it executable and owned by root:nut:
chmod +x /etc/nut/notify.sh
chown root:nut /etc/nut/notify.sh
Test to ensure that emails transmissions are working.
Starting and Enabling Services
Now start the NUT services in order and enable them on boot:
# Start the UPS driver
systemctl start nut-driver@pve_ups
systemctl enable nut-driver@pve_ups
# Start upsd (the UPS network daemon)
systemctl start nut-server
systemctl enable nut-server
# Start upsmon (the monitoring daemon)
systemctl start nut-monitor
systemctl enable nut-monitor
Check their status:
systemctl status nut-driver@pve_ups nut-server nut-monitor
Expected output (active/running) should indicate all services are up. For example:
● nut-driver@pve_ups.service - Network UPS Tools - device driver for NUT device 'pve_ups'
Loaded: loaded (/usr/lib/systemd/system/nut-driver@.service; enabled; preset: enabled)
Drop-In: /etc/systemd/system/nut-driver@pve_ups.service.d
└─nut-driver-enumerator-generated-checksum.conf, nut-driver-enumerator-generated.conf
Active: active (running) since Thu 2026-03-05 16:39:06 CET; 27min ago
Invocation: e6871899709b42759300b9b8f9b913a6
Process: 670956 ExecStartPre=/usr/bin/systemd-tmpfiles --create /usr/lib/tmpfiles.d/nut-common-tmpfiles.conf (code=exited, status=0/SUCCESS)
Process: 670958 ExecStart=/bin/sh -c NUTDEV="`/usr/libexec/nut-driver-enumerator.sh --get-device-for-service pve_ups`" && [ -n "$NUTDEV" ] || { echo "FATAL: Could not find a NUT device section for service unit pve_ups" >&2 ; exit 1 >
Main PID: 671052 (blazer_usb)
Tasks: 1 (limit: 154028)
Memory: 976K (peak: 5.2M)
CPU: 295ms
CGroup: /system.slice/system-nut\x2ddriver.slice/nut-driver@pve_ups.service
└─671052 /usr/lib/nut/blazer_usb -a pve_ups
Mar 05 16:39:05 pve blazer_usb[670985]: No values provided for battery high/low voltages in ups.conf
Mar 05 16:39:05 pve blazer_usb[670985]: Using 'guesstimation' (low: 20.800000, high: 26.000000)!
Mar 05 16:39:05 pve blazer_usb[670985]: Battery runtime will not be calculated (runtimecal not set)
Mar 05 16:39:06 pve blazer_usb[671052]: Startup successful
Mar 05 16:39:06 pve nut-driver@pve_ups[670958]: Network UPS Tools - UPS driver controller 2.8.1
Mar 05 16:39:06 pve blazer_usb[671052]: upsnotify: notify about state 2 with libsystemd: was requested, but not running as a service unit now, will not spam more about it
Mar 05 16:39:06 pve systemd[1]: Started nut-driver@pve_ups.service - Network UPS Tools - device driver for NUT device 'pve_ups'.
Mar 05 16:39:06 pve blazer_usb[671052]: upsnotify: failed to notify about state 2: no notification tech defined, will not spam more about it
Mar 05 16:39:06 pve blazer_usb[671052]: upsnotify: logged the systemd watchdog situation once, will not spam more about it
Mar 05 16:39:07 pve blazer_usb[671052]: sock_connect: enabling asynchronous mode (auto)
● nut-server.service - Network UPS Tools - power devices information server
Loaded: loaded (/usr/lib/systemd/system/nut-server.service; enabled; preset: enabled)
Active: active (running) since Wed 2026-03-04 14:10:17 CET; 1 day 2h ago
Invocation: 1b3b4b947bd9434fb671065fc9a6ccec
Process: 670778 ExecReload=/usr/sbin/upsd -c reload -P $MAINPID (code=exited, status=0/SUCCESS)
Main PID: 61357 (upsd)
Tasks: 1 (limit: 154028)
Memory: 1M (peak: 3.2M)
CPU: 4.029s
CGroup: /system.slice/nut-server.service
└─61357 /lib/nut/upsd -F
Mar 05 16:38:43 pve nut-server[61357]: mainloop: Interrupted system call
Mar 05 16:38:43 pve nut-server[61357]: SIGHUP: reloading configuration
Mar 05 16:38:43 pve nut-server[61357]: Applying debug level 0 from original command line arguments
Mar 05 16:38:43 pve upsd[61357]: SIGHUP: reloading configuration
Mar 05 16:38:43 pve upsd[61357]: Applying debug level 0 from original command line arguments
Mar 05 16:38:43 pve systemd[1]: Reloaded nut-server.service - Network UPS Tools - power devices information server.
Mar 05 16:38:55 pve nut-server[61357]: Connected to UPS [pve_ups]: blazer_usb-pve_ups
Mar 05 16:38:55 pve upsd[61357]: Connected to UPS [pve_ups]: blazer_usb-pve_ups
Mar 05 16:39:07 pve nut-server[61357]: Connected to UPS [pve_ups]: blazer_usb-pve_ups
Mar 05 16:39:07 pve upsd[61357]: Connected to UPS [pve_ups]: blazer_usb-pve_ups
● nut-monitor.service - Network UPS Tools - power device monitor and shutdown controller
Loaded: loaded (/usr/lib/systemd/system/nut-monitor.service; enabled; preset: enabled)
Active: active (running) since Wed 2026-03-04 15:53:25 CET; 1 day 1h ago
Invocation: e34c6442628343bfb3500fc8a01c2fea
Main PID: 107450 (upsmon)
Tasks: 2 (limit: 154028)
Memory: 1.1M (peak: 6M)
CPU: 1.828s
CGroup: /system.slice/nut-monitor.service
├─107450 /lib/nut/upsmon -F
└─107451 /lib/nut/upsmon -F
Mar 05 16:38:43 pve nut-monitor[107451]: Poll UPS [pve_ups@localhost] failed - Driver not connected
Mar 05 16:38:48 pve nut-monitor[107451]: Poll UPS [pve_ups@localhost] failed - Driver not connected
Mar 05 16:38:53 pve nut-monitor[107451]: Poll UPS [pve_ups@localhost] failed - Driver not connected
Mar 05 16:38:58 pve nut-monitor[107451]: Communications with UPS pve_ups@localhost established
Mar 05 16:38:58 pve nut-monitor[670944]: Network UPS Tools upsmon 2.8.1
Mar 05 16:39:03 pve nut-monitor[107451]: Poll UPS [pve_ups@localhost] failed - Driver not connected
Mar 05 16:39:03 pve nut-monitor[107451]: Communications with UPS pve_ups@localhost lost
Mar 05 16:39:03 pve nut-monitor[671005]: Network UPS Tools upsmon 2.8.1
Mar 05 16:39:08 pve nut-monitor[107451]: Communications with UPS pve_ups@localhost established
Mar 05 16:39:08 pve nut-monitor[671063]: Network UPS Tools upsmon 2.8.1
This confirms the driver (blazer_usb), upsd, and upsmon daemons are running and connected to the UPS.
USB Permissions (udev Rule)
To ensure NUT can always access the UPS USB device after reboots, add a udev rule:
nano /etc/udev/rules.d/99-nut-ups.rules
ATTR{idVendor}<mark style="background-color: #fff3cd; padding: 2px 4px;">"0665", ATTR{idProduct}</mark>"5161", MODE="0660", GROUP="nut"
Use your vendor/product IDs. Then reload udev rules:
udevadm control --reload-rules
udevadm trigger
This makes the UPS device always belong to group nut with correct permissions, so nut -group processes can access it.
Testing Shutdown Logic
Perform controlled tests (save work before doing this):
Forced Shutdown Command
Send an FSD (forced shutdown) flag to upsmon:
# Send the FSD (Forced Shutdown) command
upsmon -c fsd
This should start the shutdown sequence immediately.
Unplug UPS
Disconnect mains power from the UPS. Then run:
watch -n 2 upsc pve_ups@localhost
You should see ups.status change from OL (on line) to OB (on battery). and the battery.charge start to go down.
Also monitor logs:
journalctl -f -g 'ups|nut' --case-sensitive=false --no-pager
Verify that upsmon logs a low-battery or on-battery event and triggers shutdown after the configured delay.
Low Battery Simulation
If your UPS firmware allows changing the low threshold, you can trigger a LOWBATT event manually. In one terminal:
journalctl -f -g 'ups|nut' --case-sensitive=false --no-pager
In another:
upsrw -s battery.charge.low=99 -u upsadmin -p upsadmin_pass pve_ups@localhost
This artificially raises the low threshold above the current charge, causing upsmon to see a LOWBATT event. (If the charge is at 100 just unplug the UPS for a few seconds. The first terminal’s logs should show the notification.
Then plug in the UPS and restore the settings
upsrw -s battery.charge.low=40 -u upsadmin -p upsadmin_pass pve_ups@localhost
<





Comments
Post a Comment