How to Install NUT (Network UPS Tools) to Monitor Your UPS and Protect Your Linux Server During Power Outages

Monitoring tutorial - IT technology blog
Monitoring tutorial - IT technology blog

Last month, my production server went down without warning at 2 AM. Not a kernel panic, not the OOM killer acting up — just a sudden power outage. The UPS ran for eight minutes before the battery died, and the server had no idea it was happening. The result: a corrupted PostgreSQL instance, nearly four hours spent restoring from backup, and a Japanese client calling first thing in the morning asking why the system was dead.

The UPS was right there, the battery was still good — but nothing was connecting the two together. That’s when I started taking NUT seriously.

Three Ways to Monitor a UPS on Linux: Compare Before You Choose

My first search was “linux ups monitoring” — it took about 20 minutes to realize there are three distinct approaches, each with clear trade-offs:

Option 1: Vendor-Proprietary Software

APC has PowerChute, Eaton has Intelligent Power Manager, CyberPower has PowerPanel. Plug in the USB, install the software, done. Sounds simple enough.

Pros: Clean interface, official vendor support, deep integration with their own UPS models.

Cons: Heavy vendor lock-in. APC PowerChute only works with APC UPS units. Eaton only works with Eaton. Switching UPS brands means starting from scratch. Enterprise features usually require a paid license, and the Linux documentation is noticeably weaker than the Windows version.

Option 2: Custom Scripts with Cron

In theory it sounds reasonable: read USB HID data from the UPS, write a bash script to check the battery every minute, trigger a shutdown when it drops below a threshold.

Pros: Full control — you understand every line of code running on your server.

Cons: Reinventing the wheel is more costly than it seems. UPS communication protocols are complex, and every model has its own quirks. I tried this once with an old CyberPower unit and spent two days debugging why the script was reading the wrong battery percentage. Not worth it.

Option 3: NUT (Network UPS Tools)

Open source, supports 170+ protocols and thousands of UPS models from APC, Eaton, CyberPower, Belkin, and more. It uses a client-server architecture: one machine connects directly to the UPS (NUT server), and other machines monitor it over the network (NUT clients). Available in every major Linux distribution’s package manager.

Pros: Vendor-neutral, battle-tested, supports multiple servers from a single UPS.

Cons: The config files can be verbose, and the documentation is sometimes outdated. But once it’s set up, you barely need to touch it again.

NUT Is the Right Choice — Here’s Why

If you only have one fixed UPS model and you’re certain you’ll never change it, proprietary software is fine. But if you want a long-term, multi-server solution — or just something you can actually Google when something goes wrong — NUT is the clear winner.

Installing and Configuring NUT from Scratch

Step 1: Verify the UPS Is Detected Over USB

Plug the USB cable from the UPS into the server, then run:

lsusb | grep -i "American Power\|Eaton\|CyberPower\|Tripplite\|UPS"

Sample output for an APC UPS:

Bus 001 Device 003: ID 051d:0002 American Power Conversion Uninterruptible Power Supply

If it shows up, the USB layer is working. You don’t have a driver yet, but it’s a good sign to continue.

Step 2: Install NUT

On Ubuntu/Debian:

sudo apt update
sudo apt install nut nut-client nut-server

On CentOS/RHEL/Rocky Linux:

sudo dnf install nut

Step 3: Find the Right Driver

Picking the wrong driver is a dead end — NUT won’t be able to read anything from the UPS. Fortunately, NUT includes an auto-scan tool:

sudo nut-scanner -U

Sample output with a suggested configuration:

[nutdev1]
        driver = "usbhid-ups"
        port = "auto"
        vendorid = "051D"
        productid = "0002"
        product = "Back-UPS RS 1500MS2"
        vendor = "American Power Conversion"
        bus = "001"

Copy this block — you’ll use it in the next step.

Step 4: Configure ups.conf

sudo nano /etc/nut/ups.conf

Add the following (using the output from nut-scanner above):

[myups]
    driver = usbhid-ups
    port = auto
    desc = "APC Back-UPS RS 1500"

The name in square brackets (myups) is a label you define yourself — it’s referenced in the other config files.

Step 5: Configure nut.conf — Choose a Mode

sudo nano /etc/nut/nut.conf

If only one server connects directly to the UPS:

MODE=standalone

If this server will act as a UPS server for other machines on the network:

MODE=netserver

Step 6: Configure upsd.conf and upsd.users

sudo nano /etc/nut/upsd.conf
# Only listen locally if standalone
LISTEN 127.0.0.1 3493

# Open to the network if other machines need to connect
# LISTEN 0.0.0.0 3493

Create credentials for upsmon to authenticate with upsd:

sudo nano /etc/nut/upsd.users
[upsmon]
    password = your_secure_password_here
    upsmon master

Step 7: Configure upsmon.conf — The Brain That Issues the Shutdown Command

sudo nano /etc/nut/upsmon.conf
MONITOR myups@localhost 1 upsmon your_secure_password_here master

MINSUPPLIES 1
SHUTDOWNCMD "/sbin/shutdown -h now"
POLLFREQ 5
POLLFREQALERT 5
HOSTSYNC 15
DEADTIME 15

POWERDOWNFLAG /etc/killpower

RBWARNTIME 43200
NOCOMMWARNTIME 300
FINALDELAY 5

SHUTDOWNCMD is the heart of the entire setup — this command runs when NUT decides it’s time to safely shut down the machine. Before applying, run which shutdown to confirm the correct path on your system.

Step 8: Start and Verify

sudo systemctl start nut-server
sudo systemctl start nut-client
sudo systemctl enable nut-server nut-client

# Start the UPS driver
sudo upsdrvctl start

# Read real-time UPS data
upsc myups@localhost

Output when the connection is successful:

battery.charge: 100
battery.charge.low: 10
battery.runtime: 1428
input.voltage: 229.0
output.voltage: 229.0
ups.load: 23
ups.status: OL

ups.status: OL = On Line (running on grid power, normal). OB = On Battery (running on battery). Seeing OL means the setup is working.

Alert Fatigue: A Hard Lesson Learned in Production

I fell right into this trap when I first set it up — it took several rounds of tuning to get the thresholds right. My initial settings of POLLFREQ 2 and DEADTIME 10 were far too aggressive. The result was constant alerts every time the grid voltage dipped slightly — in Japan, brief voltage sags of a few seconds are not uncommon. The Telegram bot was pinging me at 3 AM so often that I turned off notifications, and then missed a real alert the following week.

Values that worked well after several rounds of tuning:

# In upsmon.conf
POLLFREQ 5           # Check every 5 seconds under normal conditions
POLLFREQALERT 5      # Check every 5 seconds while on battery
DEADTIME 15          # Declare connection lost after 15 seconds

# Override battery low threshold (default is usually 10%)
# Add to ups.conf if you want an earlier shutdown:
# override.battery.charge.low = 30

Configuring Alerts When Power Fails

Nobody wants to find out their server went down through a customer message at the crack of dawn. Add this to upsmon.conf to have NUT alert you proactively:

NOTIFYCMD /etc/nut/notify.sh

NOTIFYFLAG ONLINE    SYSLOG+WALL+EXEC
NOTIFYFLAG ONBATT    SYSLOG+WALL+EXEC
NOTIFYFLAG LOWBATT   SYSLOG+WALL+EXEC
NOTIFYFLAG COMMBAD   SYSLOG+WALL+EXEC

Create a script to send email alerts (or replace with a curl call to Telegram):

sudo nano /etc/nut/notify.sh
#!/bin/bash
# $1 = message from NUT (e.g., "UPS myups on battery")
echo "$1" | mail -s "[UPS Alert] $(hostname)" [email protected]
sudo chmod +x /etc/nut/notify.sh

Verifying the Entire Setup Works Correctly

The safest way to test is with these commands — do NOT pull the actual power plug:

# View logs in real time
sudo journalctl -u nut-server -f
sudo journalctl -u nut-client -f

# Check individual metrics
upsc myups@localhost ups.status
upsc myups@localhost battery.charge
upsc myups@localhost battery.runtime

# Force shutdown to test the shutdown path (WARNING: the machine will actually shut down)
# sudo upsmon -c fsd

After verifying everything, I added a weekly cron check:

# crontab -e
0 9 * * 1 /usr/bin/upsc myups@localhost battery.charge | mail -s "Weekly UPS Battery: $(hostname)" [email protected]

Every Monday I get an email with the battery health figure. If the email doesn’t arrive, I know something is wrong with NUT or the UPS — simple, and far more effective than waiting for an incident to discover the problem.

Wrapping Up

NUT isn’t complicated once you understand the architecture: the driver reads data from the UPS, the upsd daemon exposes that data over the network, and upsmon watches it and issues the shutdown command when needed. It takes about 30 minutes to get a working setup from scratch, and after that it barely needs any maintenance. My only regret is not doing it sooner — before that night when PostgreSQL got corrupted.

Share: