The UPS looked ordinary right up until NUT refused to see it the way I expected.
These Prolink 650VA and 850VA units do not behave like the USB HID UPS gear many people assume they are. Both units showed up as 0665:5161 USB-to-serial devices, and the shortest working path was to use nutdrv_qx, not usbhid-ups.
This post is the cleaned-up version of the setup I ended up with across two Linux hosts:
- a Debian NUC in the PC room acting as a NUT server
- a Proxmox node in the living room acting as a second NUT server and client endpoint for Synology DSM
I am keeping the example hostnames, IPs and tokens generic. Replace them with your own values.
What mattered first
The first useful discovery was simple:
- both UPS units exposed as
0665:5161 INNO TECH/Cypress USB to Serial nutdrv_qxwas the driver that workedusbhid-upswas the wrong choice for these units- on Proxmox, run the commands as
root - on Debian, use
sudounless you are already root
Before you touch NUT config, make sure the host can actually see the device:
lsusb | grep 0665
nut-scanner -U
A working scan should look broadly like this:
[nutdev1]
driver = "nutdrv_qx"
port = "auto"
vendorid = "0665"
productid = "5161"
product = "USB to Serial"
vendor = "INNO TECH"
If you do not get something in that shape, stop there. Fix device detection before chasing NUT config.
Debian NUC Machine: NUT server for the PC room UPS
On the Debian host, install NUT first:
sudo apt update
sudo apt install -y nut
Set NUT to server mode:
sudo tee /etc/nut/nut.conf > /dev/null <<'EOF'
MODE=netserver
EOF
Define the UPS in /etc/nut/ups.conf:
sudo tee /etc/nut/ups.conf > /dev/null <<'EOF'
[pcroomups]
driver = nutdrv_qx
port = auto
vendorid = 0665
productid = 5161
desc = "PC Room UPS"
EOF
Let upsd listen on the network:
sudo tee /etc/nut/upsd.conf > /dev/null <<'EOF'
LISTEN 0.0.0.0 3493
EOF
Create a local monitor account. Use a real password on your host, not the placeholder below:
sudo tee /etc/nut/upsd.users > /dev/null <<'EOF'
[localmon]
password = CHANGE_THIS_LONG_RANDOM_PASSWORD
upsmon primary
EOF
Then configure upsmon:
sudo tee /etc/nut/upsmon.conf > /dev/null <<'EOF'
MONITOR pcroomups@localhost 1 localmon CHANGE_THIS_LONG_RANDOM_PASSWORD primary
MINSUPPLIES 1
SHUTDOWNCMD "/sbin/shutdown -h now"
POWERDOWNFLAG /etc/killpower
NOTIFYCMD /etc/nut/notify.sh
NOTIFYFLAG ONLINE SYSLOG
NOTIFYFLAG ONBATT SYSLOG+EXEC
NOTIFYFLAG LOWBATT SYSLOG+EXEC
NOTIFYFLAG FSD SYSLOG+EXEC
NOTIFYFLAG SHUTDOWN SYSLOG+EXEC
EOF
Telegram notifications
I kept Telegram credentials outside the script so the main file stays clean.
sudo tee /etc/nut/telegram.env > /dev/null <<'EOF'
TELEGRAM_BOT_TOKEN="YOUR_BOT_TOKEN"
TELEGRAM_CHAT_ID="YOUR_CHAT_ID"
EOF
sudo chown root:nut /etc/nut/telegram.env
sudo chmod 640 /etc/nut/telegram.env
Notification script
This script handles the useful states: power restored, on battery, low battery, forced shutdown and communication events.
It also includes a remote shutdown hook for another Linux box. The Windows path was later disabled in my setup because that desktop was no longer on this UPS.
sudo tee /etc/nut/notify.sh > /dev/null <<'EOF'
#!/usr/bin/env bash
set -u
LOG_TAG="nut-pcroom"
TELEGRAM_ENV="/etc/nut/telegram.env"
REMOTE_HOST_IP="192.0.2.10"
REMOTE_SSH_TARGET="[email protected]"
REMOTE_SHUTDOWN_CMD="/usr/local/bin/shutdown-safe.sh --execute"
load_telegram_config() {
if [ -r "$TELEGRAM_ENV" ]; then
# shellcheck disable=SC1090
. "$TELEGRAM_ENV"
fi
}
log_msg() {
logger -t "$LOG_TAG" "$1"
}
send_telegram() {
local text="$1"
load_telegram_config
if [ -z "${TELEGRAM_BOT_TOKEN:-}" ] || [ -z "${TELEGRAM_CHAT_ID:-}" ]; then
log_msg "Telegram config missing; message not sent: $text"
return 0
fi
curl -fsS \
--connect-timeout 5 \
--max-time 15 \
--data-urlencode "chat_id=${TELEGRAM_CHAT_ID}" \
--data-urlencode "text=${text}" \
"https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \
>/dev/null 2>&1 || log_msg "Telegram send failed: $text"
}
is_reachable() {
local ip="$1"
ping -c1 -W2 "$ip" >/dev/null 2>&1
}
shutdown_remote_host_if_awake() {
if is_reachable "$REMOTE_HOST_IP"; then
log_msg "Remote host reachable; sending shutdown command"
send_telegram "đ PC Room UPS: shutting down remote host."
ssh \
-o BatchMode=yes \
-o ConnectTimeout=8 \
-o StrictHostKeyChecking=no \
"$REMOTE_SSH_TARGET" \
"$REMOTE_SHUTDOWN_CMD" &
else
log_msg "Remote host not reachable; skipping"
send_telegram "âšī¸ PC Room UPS: remote host not reachable, skipping shutdown."
fi
}
shutdown_self() {
log_msg "Self shutdown: NUC shutting down now"
send_telegram "đģ PC Room UPS: NUC is shutting itself down now."
sleep 2
/sbin/shutdown -h now
}
notify_type="${NOTIFYTYPE:-UNKNOWN}"
log_msg "NUT event: ${notify_type}"
case "$notify_type" in
ONLINE)
log_msg "Power restored"
send_telegram "â
PC Room power restored. UPS is back online."
;;
ONBATT)
log_msg "UPS on battery"
send_telegram "⥠PC Room UPS is on battery. Monitoring for low battery."
;;
LOWBATT|FSD)
log_msg "Critical UPS event ${notify_type}; initiating shutdown sequence"
send_telegram "đ PC Room UPS critical event: ${notify_type}. Starting shutdown sequence."
shutdown_remote_host_if_awake
wait
send_telegram "â
PC Room UPS: remote shutdown commands completed. NUC will shut down last."
shutdown_self
;;
SHUTDOWN)
log_msg "NUT SHUTDOWN event received"
send_telegram "â ī¸ PC Room UPS: NUT SHUTDOWN event received."
;;
COMMBAD)
log_msg "UPS communication lost"
send_telegram "â ī¸ PC Room UPS communication lost."
;;
COMMOK)
log_msg "UPS communication restored"
send_telegram "â
PC Room UPS communication restored."
;;
*)
log_msg "Unhandled NUT event: ${notify_type}"
send_telegram "âšī¸ PC Room UPS event: ${notify_type}"
;;
esac
exit 0
EOF
sudo chown root:nut /etc/nut/notify.sh
sudo chmod 750 /etc/nut/notify.sh
Set the permissions and start the services:
sudo chown root:nut /etc/nut/*.conf /etc/nut/upsd.users
sudo chmod 640 /etc/nut/*.conf /etc/nut/upsd.users
sudo install -d -m 770 -o nut -g nut /run/nut
sudo systemctl restart nut-server nut-monitor
sudo systemctl enable nut-server nut-monitor
Verify the Debian host
Check that NUT is responding:
upsc pcroomups@localhost
A healthy result should include values in this ballpark:
driver.name: nutdrv_qx
ups.status: OL
battery.charge: 100
Proxmox node: second NUT server for the living room UPS
On the Proxmox node, run the commands as root.
Install NUT:
apt update
apt install -y nut
Set NUT to server mode:
tee /etc/nut/nut.conf > /dev/null <<'EOF'
MODE=netserver
EOF
Use ups as the UPS name. That keeps the naming compatible with Synology DSM expectations in my setup.
tee /etc/nut/ups.conf > /dev/null <<'EOF'
[ups]
driver = nutdrv_qx
port = auto
vendorid = 0665
productid = 5161
desc = "Living Room UPS"
EOF
Allow upsd to listen on the LAN:
tee /etc/nut/upsd.conf > /dev/null <<'EOF'
LISTEN 0.0.0.0 3493
EOF
Create a local monitor account plus a client account for DSM:
tee /etc/nut/upsd.users > /dev/null <<'EOF'
[localmon]
password = CHANGE_THIS_LONG_RANDOM_PASSWORD
upsmon primary
[monuser]
password = secret
upsmon secondary
EOF
If DSM has trouble connecting, the client role may need adjustment on older versions. In my notes, upsmon slave was the fallback to try.
Configure upsmon:
tee /etc/nut/upsmon.conf > /dev/null <<'EOF'
MONITOR ups@localhost 1 localmon CHANGE_THIS_LONG_RANDOM_PASSWORD primary
MINSUPPLIES 1
SHUTDOWNCMD "/sbin/shutdown -h now"
POWERDOWNFLAG /etc/killpower
POLLFREQ 5
POLLFREQALERT 5
HOSTSYNC 30
DEADTIME 20
FINALDELAY 5
NOTIFYCMD /usr/sbin/upssched
NOTIFYFLAG ONLINE SYSLOG+EXEC
NOTIFYFLAG ONBATT SYSLOG+EXEC
NOTIFYFLAG LOWBATT SYSLOG+EXEC
NOTIFYFLAG FSD SYSLOG+EXEC
NOTIFYFLAG SHUTDOWN SYSLOG+EXEC
EOF
Set up upssched so the node can wait for a bit before forcing shutdown:
tee /etc/nut/upssched.conf > /dev/null <<'EOF'
CMDSCRIPT /usr/local/sbin/nut-upssched-cmd.sh
PIPEFN /run/nut/upssched.pipe
LOCKFN /run/nut/upssched.lock
AT ONBATT * EXECUTE onbatt-alert
AT ONLINE * EXECUTE power-restored
AT ONBATT * START-TIMER onbatt-shutdown 300
AT ONLINE * CANCEL-TIMER onbatt-shutdown power-restored
AT LOWBATT * EXECUTE lowbatt-shutdown
AT FSD * EXECUTE fsd-shutdown
EOF
Then add the scheduler command script:
tee /usr/local/sbin/nut-upssched-cmd.sh > /dev/null <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
LOG_TAG="nut-upssched-node02"
BOT_TOKEN="YOUR_BOT_TOKEN"
CHAT_ID="YOUR_CHAT_ID"
send_telegram() {
local text="$1"
curl -fsS \
--data-urlencode "chat_id=${CHAT_ID}" \
--data-urlencode "text=${text}" \
"https://api.telegram.org/bot${BOT_TOKEN}/sendMessage" \
>/dev/null 2>&1 || logger -t "$LOG_TAG" "Telegram send failed"
}
log() { logger -t "$LOG_TAG" "$1"; }
force_nut_shutdown() {
log "Triggering NUT forced shutdown state on Proxmox node."
send_telegram "đ Living Room UPS shutdown triggered. Synology should enter safe mode; this node will shut down last."
/sbin/upsmon -c fsd
}
case "${1:-unknown}" in
onbatt-alert)
log "Living Room UPS is on battery."
send_telegram "⥠Living Room UPS is on battery."
;;
power-restored)
log "Living Room power restored."
send_telegram "â
Living Room power restored. UPS shutdown timer cancelled."
;;
onbatt-shutdown|lowbatt-shutdown)
log "Living Room UPS shutdown event: ${1}"
force_nut_shutdown
;;
fsd-shutdown)
log "Living Room UPS entered FSD state."
;;
*)
log "Unhandled upssched event: ${1:-unknown}"
;;
esac
EOF
chmod 750 /usr/local/sbin/nut-upssched-cmd.sh
chown root:nut /usr/local/sbin/nut-upssched-cmd.sh
Set permissions and start the services:
chown root:nut /etc/nut/*.conf /etc/nut/upsd.users
chmod 640 /etc/nut/*.conf /etc/nut/upsd.users
install -d -m 770 -o nut -g nut /run/nut
systemctl restart nut-server nut-monitor
systemctl enable nut-server nut-monitor
Verify the Proxmox node
upsc ups@localhost
upsc [email protected]
ss -tlnp | grep 3493
You want to see the usual signs of life:
driver.name: nutdrv_qx
ups.status: OL
battery.charge: 100
LISTEN ... 0.0.0.0:3493
If you get Driver not connected
This was the annoying part that tends to waste time. If the driver fails to connect, use a recovery sequence on the affected host.
Run this as root, or prefix with sudo on Debian:
systemctl stop nut-monitor nut-server
pkill -9 -f nutdrv_qx || true
pkill -9 -f usbhid-ups || true
pkill -9 -f upsdrvctl || true
find /run/nut -mindepth 1 ! -type d -delete
install -d -m 770 -o nut -g nut /run/nut
upsdrvctl -u nut start
ls -la /run/nut
systemctl start nut-server
sleep 2
systemctl start nut-monitor
upsc <ups-name>@localhost
The socket should end up owned by nut:nut.
srw-rw---- nut nut nutdrv_qx-<ups-name>
Synology DSM as a NUT client
On DSM, the UPS settings live under:
Control Panel -> Hardware & Power -> UPS
Choose the Synology UPS Server option and point it at the Proxmox node.
The client side in my notes used a standard NUT account like this:
UPS name: ups
Username: monuser
Password: secret
DSM may not show explicit username and password fields in every version. If it connects, you should see something like:
Status: Connected
Battery Charged: 100%
Manufacturer: Generic USB to Serial
Model: UPS
Safe tests first
If you only want to verify alert handling, run the non-destructive paths:
/usr/local/sbin/nut-upssched-cmd.sh onbatt-alert
/usr/local/sbin/nut-upssched-cmd.sh power-restored
Do not run the shutdown paths unless you are prepared for the machine to power off:
/usr/local/sbin/nut-upssched-cmd.sh onbatt-shutdown
/usr/local/sbin/nut-upssched-cmd.sh lowbatt-shutdown
upsmon -c fsd
Real power test
For an actual battery test, unplug the UPS from wall power. Do not pull the USB cable if your goal is to confirm battery behavior.
You should see the status switch to battery:
upsc <ups>@localhost | grep ups.status
Expected:
ups.status: OB
When utility power comes back, the UPS should return to online state:
ups.status: OL
One last practical warning: UPS load matters
The PC room UPS started complaining when I launched a Steam game, even though there was no power cut. That is usually overload or something very close to it, not a mysterious software bug.
Watch the live load from the server:
watch -n 1 'upsc pcroomups@localhost | grep -E "ups.status|ups.load|battery.charge|input.voltage|output.voltage"'
Warning signs include:
ups.load: 80+
ups.status: OL OVER
ups.status includes OVER
frequent audible alarm while input voltage remains normal
In practice, the 850VA unit is best reserved for lighter loads. Keep the NUC and only the devices that truly need clean shutdown protection on battery. Gaming PCs, gaming monitors and other high-draw accessories are often better off on surge-only protection unless the UPS rating is comfortably above the real load.
If you need to protect a gaming machine under real GPU load, aim for something closer to a 1500VA / 900W class UPS rather than hoping a small unit will quietly absorb an RTX 3080 Ti and its transient spikes. That hope usually lasts until the first loud beep.
Takeaway
The shortest working path for these Prolink USB UPS units was not fancy:
- identify the device correctly as USB-to-serial
- use
nutdrv_qx - make NUT listen on the network where needed
- keep notifications and shutdown logic in separate scripts
- verify with
upscbefore trusting any shutdown automation
That was enough to turn a confusing USB device into something predictable and useful.
Comments