This guide documents getting the Powercool 1000VA and 850VA USB UPS units working with Network UPS Tools (NUT) on FreeBSD, including the FreeBSD-specific parts that were not immediately obvious.
This is not intended as a generic “NUT supports everything” guide. These are two UPS models I have physically tested and now have running on production FreeBSD systems.
The configurations here have been tested with:
- Powercool 1000VA
- Powercool 850VA
- FreeBSD 15.1
- NUT 2.8.5_1 from
sysutils/nut
Both Powercool units identify over USB as:
Vendor ID: 0001
Product ID: 0000
Both work with NUT’s nutdrv_qx driver using the hunnox protocol.
Why I ended up testing these
This started with a Vertiv EDGE UPS which was not working correctly with NUT on FreeBSD.
I had started digging into the HID implementation with the intention of fixing it properly and eventually submitting the changes upstream. That work is still planned, but several real power cuts and an approaching weekend changed the priority somewhat.
I needed working UPS shutdown rather more urgently than I needed a perfect driver patch, so I bought a Powercool 1000VA and an 850VA without knowing whether either would work with NUT on FreeBSD.
They did, but not without a few FreeBSD-specific hoops.
Install NUT
Install NUT from the FreeBSD package repository:
pkg install nut
Find the UPS
Plug the USB cable from the UPS into the FreeBSD system and run:
usbconfig
Find the UPS and note its ugenX.Y address.
Inspect the device descriptor:
usbconfig -d ugenX.Y dump_device_desc
Both Powercool units tested here reported:
idVendor = 0x0001
idProduct = 0x0000
You can also inspect the USB interfaces:
usbconfig -d ugenX.Y dump_curr_config_desc
and, importantly:
usbconfig -d ugenX.Y show_ifdrv
That last command became rather important.
The first FreeBSD problem: the kernel grabbed the UPS
Initially the UPS was visible to FreeBSD, but NUT could not successfully claim the USB interface.
The reason was that FreeBSD’s HID support had already attached a kernel driver to it.
Before changing anything permanently, I proved that this was the problem by manually detaching the kernel driver from interface 0:
usbconfig -d ugenX.Y -i 0 detach_kernel_driver
Once the kernel driver was detached, NUT could access the device.
The permanent solution was to tell FreeBSD not to attach its HID driver to this VID/PID.
Add the following to /boot/loader.conf:
# Powercool UPS / NUT
usb_quirk_load="YES"
hw.usb.quirk.0="0x0001 0x0000 0x0000 0xffff UQ_HID_IGNORE"
If hw.usb.quirk.0 is already in use on your system, use the next available quirk number instead of overwriting an existing entry.
The loader quirk takes effect at boot. Either reboot at this point or continue using the manually detached interface for the current boot.
After rebooting, check the interface again:
usbconfig -d ugenX.Y show_ifdrv
Confirm that FreeBSD has not attached its HID driver to the UPS interface.
Do not blindly copy 0001:0000 for a different UPS. Check its VID/PID with usbconfig first.
The second FreeBSD problem: device permissions
Stopping the kernel HID driver from claiming the UPS is only half of the problem.
NUT also needs permission to access the USB device while running as the unprivileged nut user.
The FreeBSD NUT package installs USB permission rules in:
/usr/local/etc/devd/nut-usb.conf
Check whether the UPS is covered by the installed rules:
grep -n -i '0x0001' /usr/local/etc/devd/nut-usb.conf
After installing or upgrading NUT, restart devd:
service devd restart
Then disconnect and reconnect the UPS USB cable and check the device permissions:
ls -l /dev/ugenX.Y
If the package-provided rule covers the device and the permissions are correct, no additional devfs rules should be necessary.
On my systems I also tested the following dedicated devfs ruleset, which is useful as a simple fallback if the package rule does not match the device or permissions are still wrong.
Create /etc/devfs.rules:
[ups=10]
add path 'ugen*' mode 0660 group nut
add path 'usb/*' mode 0660 group nut
Enable that ruleset in /etc/rc.conf:
sysrc devfs_system_ruleset="ups"
Then apply it:
service devfs restart
This gives members of the nut group access to both the ugen devices and the underlying /dev/usb/* device nodes.
The fallback rules are deliberately simple and are known to work with these systems, but they grant the nut group access to all matching USB device nodes. If the package-provided devd rule works, it is the narrower option.
FreeBSD startup configuration
The NUT-related entries in /etc/rc.conf are:
# NUT
devfs_system_ruleset="ups"
nut_enable="YES"
nut_upsmon_enable="YES"
nut_upsshut="YES"
The supplied /usr/local/etc/nut/nut.conf remains:
MODE=none
on both of my working FreeBSD systems.
That may look odd if you have followed Linux NUT guides which tell you to select standalone, netserver or netclient, but these FreeBSD installations are being started through the FreeBSD rc configuration above.
Powercool 1000VA configuration
Create the UPS definition in:
/usr/local/etc/nut/ups.conf
For the tested Powercool 1000VA:
[powercool]
driver = nutdrv_qx
port = auto
desc = "Powercool 1000VA"
vendorid = 0001
productid = 0000
protocol = hunnox
langid_fix = 0x0409
novendor
noscanlangid
default.battery.voltage.high = 27.40
default.battery.voltage.low = 21.00
default.battery.voltage.nominal = 24.00
override.battery.charge.low = 30
offdelay = 60
ondelay = 120
The tested 1000VA unit uses a nominal 24V battery configuration.
Powercool 850VA configuration
The 850VA uses the same driver and USB/protocol configuration but different battery-voltage values:
[powercool]
driver = nutdrv_qx
port = auto
desc = "Powercool 850VA"
vendorid = 0001
productid = 0000
protocol = hunnox
langid_fix = 0x0409
novendor
noscanlangid
default.battery.voltage.high = 13.70
default.battery.voltage.low = 10.50
default.battery.voltage.nominal = 12.00
override.battery.charge.low = 30
offdelay = 60
ondelay = 120
The tested 850VA unit uses a nominal 12V battery configuration.
That difference is worth paying attention to. Do not assume another Powercool model, or even a future hardware revision of one of these models, necessarily uses the same battery configuration.
The 120-second ondelay shown here has been tested successfully on both Powercool units covered by this guide. NUT’s nutdrv_qx documentation notes that some older Qx firmware may require a longer startup delay, so do not assume 120 seconds is suitable for every UPS using this protocol.
Why nutdrv_qx and Hunnox matter
The working combination on both Powercool units is:
driver = nutdrv_qx
protocol = hunnox
They are not being driven by usbhid-ups.
The additional language and vendor options shown in the complete configurations above are also part of my known-good setup. If you are reproducing this with one of these units, I would start from the complete configuration rather than reducing it to a bare nutdrv_qx stanza.
Test the driver
Start NUT:
service nut start
Then query it:
upsc powercool@localhost
For a quick status check:
upsc powercool@localhost ups.status
With utility power present you should normally get:
OL
You can inspect the particularly interesting values with:
upsc powercool@localhost | egrep 'ups.status|battery.charge|battery.voltage|ups.delay'
With the configuration above, the UPS reports:
ups.delay.shutdown: 60
ups.delay.start: 120
The 120-second startup delay is useful in the real world.
If utility power returns after an outage, the UPS does not immediately energise the load. It waits two minutes first, reducing the chance of servers starting during unstable or rapidly flapping utility power.
Debugging nutdrv_qx
If the service will not start and you need to see what the driver is doing, stop the normal service first:
service nut stop
Then run the driver interactively:
/usr/local/libexec/nut/nutdrv_qx -a powercool -DDD
This was particularly useful while working out whether the problem was NUT itself, the protocol, USB permissions or FreeBSD having already claimed the interface.
Configure the local NUT server
For a machine which only needs local access initially, /usr/local/etc/nut/upsd.conf can contain:
LISTEN 127.0.0.1 3493
If other systems will monitor this UPS over the LAN, add the server’s LAN address as a second listener:
LISTEN 127.0.0.1 3493
LISTEN 192.168.1.10 3493
Use the real LAN address of the FreeBSD system.
There is no need to use:
LISTEN 0.0.0.0 3493
unless you genuinely want NUT listening on every IPv4 interface.
Do not expose NUT’s TCP port 3493 directly to the Internet.
After changing a LISTEN directive, restart NUT:
service nut restart
Configure NUT users
/usr/local/etc/nut/upsd.users contains the accounts used by upsmon.
For the machine directly connected to the UPS:
[monprimary]
password = CHANGE_THIS_PASSWORD
upsmon primary
If you want other machines to monitor the UPS as secondaries, add another account:
[monsubscriber]
password = CHANGE_THIS_PASSWORD_TOO
upsmon secondary
The same secondary account can be used by multiple trusted machines if desired.
I prefer keeping the primary and secondary credentials separate.
A FreeBSD permissions gotcha
This caught me after the setup appeared to be working and I rebooted.
upsd must actually be able to read its configuration and user database after dropping privileges.
The credential-bearing files should not be world-readable, but they need to be readable by the nut group.
For example:
chown root:nut /usr/local/etc/nut/upsd.conf
chown root:nut /usr/local/etc/nut/upsd.users
chown root:nut /usr/local/etc/nut/upsmon.conf
chmod 640 /usr/local/etc/nut/upsd.conf
chmod 640 /usr/local/etc/nut/upsd.users
chmod 640 /usr/local/etc/nut/upsmon.conf
This is particularly important for upsd.users and upsmon.conf, since they contain passwords.
Configure the primary upsmon
The important parts of /usr/local/etc/nut/upsmon.conf are:
MONITOR powercool@localhost 1 monprimary CHANGE_THIS_PASSWORD primary
MINSUPPLIES 1
SHUTDOWNCMD "/sbin/shutdown -p now"
POWERDOWNFLAG /etc/killpower
POLLFREQ 5
POLLFREQALERT 5
DEADTIME 15
HOSTSYNC 15
FINALDELAY 5
NOTIFYCMD /usr/local/sbin/upssched
NOTIFYFLAG ONLINE SYSLOG+EXEC
NOTIFYFLAG ONBATT SYSLOG+WALL+EXEC
NOTIFYFLAG LOWBATT SYSLOG+WALL
NOTIFYFLAG COMMBAD SYSLOG
NOTIFYFLAG COMMOK SYSLOG
NOTIFYFLAG NOCOMM SYSLOG+WALL
I am not simply waiting for the UPS to reach low battery.
For these systems the outage policy is:
- Utility power fails and the UPS switches to battery.
- After 30 seconds, the sustained-outage warning hook runs.
- If utility power returns before 180 seconds, the pending shutdown timer is cancelled.
- If the outage reaches 180 seconds, NUT starts the coordinated shutdown.
The 180-second timer is an early-shutdown policy, not an instruction to ignore a genuinely critical battery condition. If NUT considers the UPS critical before the timer expires, its normal low-battery shutdown behaviour can still take over.
The timers are handled using upssched.
Configure upssched
Create or edit:
/usr/local/etc/nut/upssched.conf
The core configuration is:
CMDSCRIPT /usr/local/bin/upssched-cmd
PIPEFN /var/db/nut/upssched/upssched.pipe
LOCKFN /var/db/nut/upssched/upssched.lock
AT ONBATT * START-TIMER onbattwarn 30
AT ONBATT * START-TIMER onbattshutdown 180
AT ONLINE * CANCEL-TIMER onbattwarn
AT ONLINE * CANCEL-TIMER onbattshutdown
Another FreeBSD gotcha: the upssched directory
The configuration refers to:
/var/db/nut/upssched/
but that directory may not exist.
Create it explicitly:
mkdir -p /var/db/nut/upssched
chown nut:nut /var/db/nut/upssched
chmod 770 /var/db/nut/upssched
A working installation looks something like:
drwxrwx--- nut nut upssched
Without a writable location for the FIFO and lock file, upssched cannot do its job.
The upssched command script
Create:
/usr/local/bin/upssched-cmd
A minimal version of the script I am using is:
#!/bin/sh
case "$1" in
onbattwarn)
logger -t upssched-cmd \
"UPS has been on battery for 30 seconds"
;;
onbattshutdown)
logger -t upssched-cmd \
"UPS has been on battery for 180 seconds - initiating shutdown"
/usr/local/sbin/upsmon -c fsd
;;
*)
logger -t upssched-cmd \
"Unrecognised command: $1"
;;
esac
Make it executable:
chmod 755 /usr/local/bin/upssched-cmd
The example above logs the 30-second event. My production version also sends an email notification at that point, but that is deliberately left out because it is site-specific and has nothing to do with getting NUT itself working.
Why use upsmon -c fsd?
When the three-minute timer expires, the script runs:
/usr/local/sbin/upsmon -c fsd
FSD means Forced Shutdown.
This allows NUT to perform its normal coordinated shutdown rather than having an arbitrary shell script issue shutdown.
The primary follows the configured NUT shutdown process, including creating:
/etc/killpower
via the configured POWERDOWNFLAG.
With:
nut_upsshut="YES"
in FreeBSD’s rc.conf, the late shutdown process can then tell the UPS driver to perform its UPS shutdown operation.
This gives us the complete chain rather than merely shutting down FreeBSD and leaving the UPS running indefinitely.
Once FSD has been issued, treat the shutdown as committed. Restoring utility power at that point does not cancel the forced shutdown. Allow the coordinated shutdown and UPS power-cycle to complete.
Start everything
Once the configuration is complete:
service devfs restart
service nut restart
service nut_upsmon restart
Then verify:
upsc powercool@localhost
and:
upsc powercool@localhost ups.status
You can also watch /var/log/messages while testing:
tail -f /var/log/messages
Testing without immediately shutting the server down
The first useful test is simply to remove mains input from the UPS while leaving the server connected to the UPS output.
Watch:
upsc powercool@localhost ups.status
The state should change from:
OL
to an on-battery state.
Reconnect utility power before the 180-second timer expires.
The pending onbattshutdown timer should be cancelled and the server should remain running.
That proves the basic ONBATT/ONLINE scheduling path without actually shutting down the machine.
Do the full shutdown test during a maintenance window.
Be aware that:
upsmon -c fsd
is not a harmless test command.
It initiates the real forced-shutdown sequence.
Monitoring one UPS from several machines
Only one machine needs the USB connection to the UPS.
The arrangement is simply:
Powercool UPS -> USB -> FreeBSD NUT primary -> TCP 3493 -> NUT secondaries
The remote systems do not need a USB UPS connection.
They simply run upsmon and subscribe to the UPS state exported by the primary.
FreeBSD secondary configuration
Install NUT:
pkg install nut
The secondary does not need a local UPS driver.
Enable upsmon in /etc/rc.conf:
nut_upsmon_enable="YES"
Then configure /usr/local/etc/nut/upsmon.conf:
MONITOR powercool@192.168.1.10 1 monsubscriber CHANGE_THIS_PASSWORD_TOO secondary
MINSUPPLIES 1
SHUTDOWNCMD "/sbin/shutdown -p now"
POLLFREQ 5
POLLFREQALERT 5
DEADTIME 15
HOSTSYNC 15
FINALDELAY 5
Replace the IP address and credentials with those for your NUT primary.
Before starting upsmon, prove that the secondary can see the UPS:
upsc powercool@192.168.1.10
or simply:
upsc powercool@192.168.1.10 ups.status
If everything is healthy:
OL
Then start or restart the monitor:
service nut_upsmon restart
When the primary issues FSD, the secondary receives that state and performs its own clean shutdown.
This is much cleaner than having the UPS-connected server SSH into every other machine and issue shutdown commands.
One last trap: will the servers actually come back on?
After getting all of this working, there is another important question.
The UPS can:
- detect the outage;
- allow the machines to shut down;
- turn its output off;
- wait for utility power to return;
- wait another 120 seconds;
- restore its output.
But will the servers actually boot?
On one Dell server I checked the power-restoration policy through the BMC from FreeBSD rather than walking downstairs to enter the BIOS.
Install IPMI tools:
pkg install ipmitools
Load the IPMI kernel module if necessary:
kldload ipmi
For permanent loading:
ipmi_load="YES"
can be placed in /boot/loader.conf.
Then check the chassis policy:
ipmitool chassis status
The server reported:
Power Restore Policy : previous
That is not what I wanted.
Because the operating system has already shut the server down cleanly before the UPS removes its output, the previous state at the point of complete AC loss can be off.
I changed the Dell to:
ipmitool chassis policy always-on
and verified:
Power Restore Policy : always-on
Now, after complete AC loss and subsequent restoration, the server powers itself back on.
Repeat this check for every server powered by the UPS that you expect to restart automatically, not just the NUT primary. Other systems may expose the same setting under a different BIOS or BMC name, such as AC recovery, power restore or restore on AC power loss.
The complete sequence
With everything configured, the intended behaviour is:
- Utility power fails and the Powercool switches to battery.
- After 30 seconds, the sustained-outage warning hook runs.
- After 180 seconds on battery,
upsmon -c fsdstarts the coordinated shutdown. - Remote NUT secondaries shut down cleanly.
- The FreeBSD primary begins its own shutdown.
/etc/killpowertells the late FreeBSD shutdown process that UPS power should be removed.- NUT requests the UPS shutdown operation.
- The UPS waits 60 seconds and turns its output off.
- Utility power eventually returns.
- The UPS waits the configured 120 seconds before restoring its output.
- Servers configured for Always On after AC loss start automatically.
Getting:
ups.status: OL
from upsc proves that NUT can communicate with the UPS. It does not prove that the complete shutdown and recovery process works.
A useful UPS deployment needs to survive the entire cycle: power failure, clean shutdown, removal of UPS output, restoration of utility power and automatic server recovery.
The Powercool 1000VA and 850VA units covered by this guide have now gone through that complete cycle successfully with NUT on FreeBSD.
What I would test before trusting it
Do not wait for the next real power cut to discover that one part of the chain does not work.
During a maintenance window I would test:
upsc powercool@localhostreturns sensible data.- Removing mains changes the UPS to an on-battery state.
- Restoring mains before 180 seconds cancels the pending timed shutdown.
- A remote NUT secondary can query the primary.
- The remote
upsmonis authenticated and running. - A sustained outage causes FSD after the expected delay.
- Secondary machines shut down cleanly.
- The primary shuts down cleanly.
- The UPS actually removes its output after the configured delay.
- Utility restoration observes the configured 120-second delay.
- UPS output returns.
- Every server which should recover automatically powers back on.