I recently added a managed UPS to a Linux server in my lab and wanted the server to do more than just sit on battery backup. I wanted the server to detect the UPS, monitor the battery status, send email alerts, provide a web status page, and shut down cleanly if power was out for too long.
The UPS I used was a CyberPower CP1500PFCLCD PFC Sinewave UPS Battery Backup and Surge Protector, 1500VA/1000W, 12 Outlets, AVR, Mini Tower, UL Certified
Amazon product page:
https://www.amazon.com/CyberPower-CP1500PFCLCD-Sinewave-Outlets-Mini-Tower/dp/B00429N19W
This post walks through the setup in a lab environment using Linux, Network UPS Tools, systemd, Nginx, and a simple notification script.
Lab Environment
The lab server used for this setup was running:
Linux distribution: LMDE 7 “Gigi”
Base: Debian 13 “Trixie”
UPS: CyberPower CP1500PFCLCD
UPS connection: USB cable
UPS software: Network UPS Tools, also known as NUT
Web server: Nginx
Mail relay: Local sendmail-compatible relay
The examples below use sanitized names and addresses:
UPS name: lab-ups
Server IP: 192.168.1.5
Alert email: admin@example.com
Status page: http://192.168.1.5/status/ups/
Substitute your own values where needed.
What I Wanted the UPS Setup to Do
The goal was not just battery backup. I wanted useful management:
- Detect the UPS over USB
- Monitor line power, battery charge, runtime, and load
- Send an email when power events happen
- Wait before shutting down, in case power comes back quickly
- Shut down cleanly if power stays out too long
- Provide a web-based UPS status page
- Start automatically after reboot
The final behavior looked like this:
- Power goes out:
- Start a 5-minute warning timer.
- Start a 30-minute shutdown timer.
- After 5 minutes on battery:
- Send an email warning.
- After 30 minutes on battery:
- Send a shutdown warning.
- Start clean shutdown.
- If power comes back:
- Cancel the timers.
- Send a recovery email.
- If the UPS reaches low battery:
- Shut down immediately.
Step 1: Plug in the UPS USB Cable
First, I connected the UPS to the Linux server using the USB cable that came with the UPS.
Then I checked whether Linux could see it:
lsusb
The UPS appeared as a CyberPower USB device. Example:
Cyber Power System, Inc. CP1500PFCLCD UPS
I also checked recent kernel messages:
dmesg | tail -n 80
This confirmed that the server saw the USB device.
Step 2: Install Network UPS Tools
Network UPS Tools, or NUT, is the standard Linux toolset for monitoring UPS units.
Install it:
apt update
apt install -y nut nut-client nut-server
Then scan for USB UPS devices:
nut-scanner -U
The scan should return something similar to this:
[nutdev1]
driver = "usbhid-ups"
port = "auto"
vendorid = "0764"
productid = "0601"
product = "CP1500PFCLCDa"
vendor = "CPS"
The important line is:
driver = "usbhid-ups"
That is the driver used for many USB HID-compatible UPS units.
Step 3: Configure the UPS
Edit `/etc/nut/ups.conf`:
cp /etc/nut/ups.conf /etc/nut/ups.conf.bak.$(date +%Y%m%d-%H%M%S)
cat > /etc/nut/ups.conf <<'EOF'
[lab-ups]
driver = usbhid-ups
port = auto
vendorid = 0764
productid = 0601
desc = "CyberPower CP1500PFCLCD UPS"
EOF
This defines the UPS as `lab-ups`.
Step 4: Set NUT to Standalone Mode
For a single server connected directly to one UPS, standalone mode is appropriate.
Configure `/etc/nut/nut.conf`:
cp /etc/nut/nut.conf /etc/nut/nut.conf.bak.$(date +%Y%m%d-%H%M%S)
cat > /etc/nut/nut.conf <<'EOF'
MODE=standalone
EOF
Step 5: Create the NUT Monitor User
NUT uses a local user account so `upsmon` can monitor the UPS through `upsd`.
Configure `/etc/nut/upsd.users`:
cp /etc/nut/upsd.users /etc/nut/upsd.users.bak.$(date +%Y%m%d-%H%M%S) 2>/dev/null || true
cat > /etc/nut/upsd.users <<'EOF'
[monuser]
password = replace-this-with-a-strong-local-password
upsmon master
EOF
chmod 640 /etc/nut/upsd.users
chown root:nut /etc/nut/upsd.users
Use a real password in your own environment.
Step 6: Configure UPS Monitoring
Configure `/etc/nut/upsmon.conf`:
cp /etc/nut/upsmon.conf /etc/nut/upsmon.conf.bak.$(date +%Y%m%d-%H%M%S)
cat > /etc/nut/upsmon.conf <<'EOF'
MONITOR lab-ups@localhost 1 monuser replace-this-with-a-strong-local-password master
MINSUPPLIES 1
SHUTDOWNCMD "/sbin/shutdown -h +0"
POLLFREQ 5
POLLFREQALERT 5
HOSTSYNC 15
DEADTIME 15
POWERDOWNFLAG /etc/killpower
NOTIFYFLAG ONLINE SYSLOG+WALL+EXEC
NOTIFYFLAG ONBATT SYSLOG+WALL+EXEC
NOTIFYFLAG LOWBATT SYSLOG+WALL+EXEC
NOTIFYFLAG FSD SYSLOG+WALL+EXEC
NOTIFYFLAG COMMOK SYSLOG+WALL+EXEC
NOTIFYFLAG COMMBAD SYSLOG+WALL+EXEC
NOTIFYFLAG SHUTDOWN SYSLOG+WALL+EXEC
NOTIFYFLAG REPLBATT SYSLOG+WALL+EXEC
NOTIFYFLAG NOCOMM SYSLOG+WALL+EXEC
NOTIFYCMD /usr/sbin/upssched
EOF
chmod 640 /etc/nut/upsmon.conf
chown root:nut /etc/nut/upsmon.conf
This tells `upsmon` to call `upssched`, which handles timed actions.
Step 7: Fix USB Permissions If Needed
On my setup, the UPS was detected but the driver initially could not open the USB device because of permissions.
The error looked like this:
libusb1: Could not open any HID devices: insufficient permissions on everything
No matching HID UPS found
The fix was to create a udev rule for the CyberPower UPS:
cat > /etc/udev/rules.d/62-cyberpower-ups.rules <<'EOF'
CyberPower CP1500PFCLCD UPS for NUT
SUBSYSTEM=="usb", ATTR{idVendor}=="0764", ATTR{idProduct}=="0601", MODE="0660", GROUP="nut"
SUBSYSTEM=="usb_device", ATTR{idVendor}=="0764", ATTR{idProduct}=="0601", MODE="0660", GROUP="nut"
EOF
udevadm control --reload-rules
udevadm trigger
Then unplug the UPS USB cable, wait a few seconds, and plug it back in.
Check the permissions:
lsusb | grep -i -E 'cyber|power|ups|0764'
for dev in /dev/bus/usb/*/*; do
if udevadm info -q property -n "$dev" 2>/dev/null | grep -q 'ID_VENDOR_ID=0764'; then
echo "===== $dev ====="
ls -l "$dev"
fi
done
The device should be owned by root with group `nut`.
Step 8: Start the NUT Services
On Debian 13, the driver runs as a systemd instance, such as:
nut-driver@lab-ups.service
Start and enable the NUT services:
systemctl daemon-reload
systemctl enable --now nut-driver-enumerator.path
systemctl enable --now nut-driver-enumerator.service
systemctl enable --now nut-server
systemctl enable --now nut-monitor
Check the driver instance:
systemctl list-units 'nut-driver*' --all --no-pager
You should see something like:
nut-driver@lab-ups.service loaded active running
Then check all three main pieces:
systemctl is-active nut-driver@lab-ups.service nut-server nut-monitor
Expected:
active
active
active
Step 9: Query the UPS
Now query the UPS:
upsc lab-ups@localhost
Useful values include:
text
battery.charge
battery.runtime
input.voltage
output.voltage
ups.load
ups.status
Example:
text
battery.charge: 100
battery.runtime: 5525
input.voltage: 120.0
output.voltage: 120.0
ups.load: 7
ups.status: OL
`OL` means the UPS is on utility power.
Common status values:
text
OL On line power
OB On battery
LB Low battery
OL CHRG On line and charging
Step 10: Create an Email Notification Script
I wanted the server to send an email when UPS events happened.
Create `/usr/local/sbin/lab-ups-notify.sh`:
cat > /usr/local/sbin/lab-ups-notify.sh <<'EOF'
!/usr/bin/env bash
TO="admin@example.com"
FROM="Lab Server <server@example.com>"
LOG="/var/log/lab-ups.log"
EVENT="${NOTIFYTYPE:-UNKNOWN}"
MESSAGE="${*:-No message supplied}"
NOW="$(date '+%Y-%m-%d %H:%M:%S %Z')"
HOST="$(hostname -f 2>/dev/null || hostname)"
UPS_STATUS="$(upsc lab-ups@localhost 2>/dev/null || true)"
{
echo "[$NOW] UPS event: $EVENT - $MESSAGE"
} >> "$LOG"
timeout 30 /usr/sbin/sendmail -t <<MAIL
From: ${FROM}
To: ${TO}
Subject: [LAB SERVER] UPS event: ${EVENT}
The lab server received a UPS event.
Time:
${NOW}
Host:
${HOST}
Event:
${EVENT}
Message:
${MESSAGE}
UPS Status:
${UPS_STATUS}
MAIL
exit 0
EOF
chmod 0755 /usr/local/sbin/lab-ups-notify.sh
Test it:
NOTIFYTYPE=TEST /usr/local/sbin/lab-ups-notify.sh "Manual UPS notification test"
If the email arrives, the notification script is working.
Step 11: Configure Timed Shutdown Behavior
I did not want the server to shut down immediately for a short power blink. I wanted it to wait.
The plan was:
- After 5 minutes on battery:
- Send warning email.
- After 30 minutes on battery:
- Start clean shutdown.
- If power comes back:
- Cancel shutdown timers and send recovery email.
- If low battery happens:
- Shut down immediately.
Create the `upssched` command script:
cat > /usr/local/sbin/lab-upssched-cmd.sh <<'EOF'
!/usr/bin/env bash
LOG="/var/log/lab-ups.log"
NOW="$(date '+%Y-%m-%d %H:%M:%S %Z')"
case "$1" in
onbatt-warning)
NOTIFYTYPE=ONBATT-WARNING /usr/local/sbin/lab-ups-notify.sh \
"The lab server has been on UPS battery power for 5 minutes."
echo "[$NOW] upssched: 5-minute on-battery warning sent" >> "$LOG"
;;
onbatt-shutdown)
NOTIFYTYPE=ONBATT-SHUTDOWN /usr/local/sbin/lab-ups-notify.sh \
"The lab server has been on UPS battery power for 30 minutes. Starting clean shutdown."
echo "[$NOW] upssched: 30-minute on-battery shutdown triggered" >> "$LOG"
/sbin/upsmon -c fsd
;;
online-recovery)
NOTIFYTYPE=ONLINE-RECOVERY /usr/local/sbin/lab-ups-notify.sh \
"Power has been restored. The lab server is back on utility power. UPS shutdown timers have been cancelled."
echo "[$NOW] upssched: online recovery email sent; shutdown timers cancelled" >> "$LOG"
;;
*)
echo "[$NOW] upssched: unknown command: $1" >> "$LOG"
;;
esac
EOF
chmod 0755 /usr/local/sbin/lab-upssched-cmd.sh
Then configure `/etc/nut/upssched.conf`:
cp /etc/nut/upssched.conf /etc/nut/upssched.conf.bak.$(date +%Y%m%d-%H%M%S) 2>/dev/null || true
cat > /etc/nut/upssched.conf <<'EOF'
CMDSCRIPT /usr/local/sbin/lab-upssched-cmd.sh
PIPEFN /run/nut/upssched.pipe
LOCKFN /run/nut/upssched.lock
AT ONBATT * START-TIMER onbatt-warning 300
AT ONBATT * START-TIMER onbatt-shutdown 1800
AT ONLINE * CANCEL-TIMER onbatt-warning
AT ONLINE * CANCEL-TIMER onbatt-shutdown
AT ONLINE * EXECUTE online-recovery
AT LOWBATT * EXECUTE onbatt-shutdown
EOF
chmod 640 /etc/nut/upssched.conf
chown root:nut /etc/nut/upssched.conf
Restart the monitor:
systemctl restart nut-monitor
Verify:
systemctl status nut-monitor --no-pager
Step 12: Install the NUT CGI Web Status Page
I also wanted a simple web page to show UPS status.
Install the packages:
apt update
apt install -y nut-cgi fcgiwrap
Find the CGI files:
dpkg -L nut-cgi | grep -E 'cgi|upsstats|upsset|hosts'
On Debian, the CGI files are usually here:
/usr/lib/cgi-bin/nut/upsstats.cgi
/usr/lib/cgi-bin/nut/upsimage.cgi
/usr/lib/cgi-bin/nut/upsset.cgi
Configure `/etc/nut/hosts.conf`:
cp /etc/nut/hosts.conf /etc/nut/hosts.conf.bak.$(date +%Y%m%d-%H%M%S)
cat > /etc/nut/hosts.conf <<'EOF'
MONITOR lab-ups@localhost "Lab UPS"
EOF
Start `fcgiwrap`:
systemctl enable --now fcgiwrap.socket
systemctl status fcgiwrap.socket --no-pager
Step 13: Add an Nginx Location for the UPS Page
In my lab, I exposed the UPS page as a subpage of the server’s local status site:
http://192.168.1.5/status/ups/
The Nginx block looked like this:
location = /status/ups {
return 301 /status/ups/;
}
location /status/ups/ {
include fastcgi_params;
fastcgi_pass unix:/run/fcgiwrap.socket;
fastcgi_param SCRIPT_FILENAME /usr/lib/cgi-bin/nut/upsstats.cgi;
fastcgi_param SCRIPT_NAME /status/ups/;
fastcgi_param PATH_INFO "";
fastcgi_param QUERY_STRING $query_string;
add_header Cache-Control "no-store, no-cache, must-revalidate, max-age=0";
}
location = /status/ups/upsimage.cgi {
include fastcgi_params;
fastcgi_pass unix:/run/fcgiwrap.socket;
fastcgi_param SCRIPT_FILENAME /usr/lib/cgi-bin/nut/upsimage.cgi;
fastcgi_param SCRIPT_NAME /status/ups/upsimage.cgi;
fastcgi_param QUERY_STRING $query_string;
add_header Cache-Control "no-store, no-cache, must-revalidate, max-age=0";
}
Test Nginx:
nginx -t
systemctl reload nginx
Then test the page:
curl -I http://192.168.1.5/status/ups/
The detailed UPS page was available at:
http://192.168.1.5/status/ups/?host=lab-ups@localhost
Step 14: Final Verification Commands
Here are the main commands I use to check the UPS setup:
systemctl is-active nut-driver@lab-ups.service nut-server nut-monitor
upsc lab-ups@localhost | grep -E 'ups.status|battery.charge|battery.runtime|ups.load|input.voltage|output.voltage'
systemctl list-units 'nut-driver*' --all --no-pager
tail -n 50 /var/log/lab-ups.log
Expected UPS status on normal wall power:
ups.status: OL
Final Result
After setup, the server had:
- UPS detected over USB
- NUT driver running
- NUT server running
- NUT monitor running
- Email alerts working
- Timed shutdown behavior configured
- Recovery email when power returns
- Web status page available on the LAN
- Automatic startup after reboot
At the current low load in my lab, the UPS reported roughly 90 minutes of runtime. I chose not to run the server all the way down. Instead, the system waits 30 minutes on battery, then shuts down cleanly unless power returns first.
That gives short outages a chance to clear while still protecting the server, filesystems, and running services from an uncontrolled power loss.
Closing Thoughts
A UPS is useful by itself, but it becomes much more useful when the server can talk to it. With a USB cable and Network UPS Tools, a Linux server can monitor power status, send alerts, display a web status page, and shut itself down cleanly.
For a home lab or small server rack, this is one of those upgrades that is worth doing before the next power outage.




