Proxmox UPS Graceful Shutdown Integration with NUT
Proxmox UPS Graceful Shutdown Integration with NUT, using custom scripts that check and may interupt shutdown process
This setup integrates Proxmox with the Network UPS Tools (NUT) system to perform a graceful shutdown of all VMs and containers in case of a power outage, and optionally cancel shutdown if power is restored.
This guide focuces only on the client side (Proxmox), if you need a guide for the server part check out this excellent guide by Techno Tim!
To make the best of this guide, it’s better to have a reparate machine running nut-server like a Raspberry Pi.
📁 Files Included
You can find all files on my github repo here
upsmon.conf- NUT monitoring client configuration.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
RUN_AS_USER root
MONITOR apc-modem@ip.address.of.nut.server 1 admin secret slave
MINSUPPLIES 1
SHUTDOWNCMD /usr/local/sbin/pve-shutdown.sh
NOTIFYCMD /usr/sbin/upssched
POLLFREQ 2
POLLFREQALERT 1
HOSTSYNC 15
DEADTIME 15
POWERDOWNFLAG /etc/killpower
NOTIFYMSG ONLINE "UPS %s on line power"
NOTIFYMSG ONBATT "UPS %s on battery"
NOTIFYMSG LOWBATT "UPS %s battery is low"
NOTIFYMSG FSD "UPS %s: forced shutdown in progress"
NOTIFYMSG COMMOK "Communications with UPS %s established"
NOTIFYMSG COMMBAD "Communications with UPS %s lost"
NOTIFYMSG SHUTDOWN "Auto logout and shutdown proceeding"
NOTIFYMSG REPLBATT "UPS %s battery needs to be replaced"
NOTIFYMSG NOCOMM "UPS %s is unavailable"
NOTIFYMSG NOPARENT "upsmon parent process died - shutdown impossible"
NOTIFYFLAG ONLINE SYSLOG+WALL+EXEC
NOTIFYFLAG ONBATT SYSLOG+WALL+EXEC
NOTIFYFLAG LOWBATT SYSLOG+WALL
NOTIFYFLAG FSD SYSLOG+WALL+EXEC
NOTIFYFLAG COMMOK SYSLOG+WALL+EXEC
NOTIFYFLAG COMMBAD SYSLOG+WALL+EXEC
NOTIFYFLAG SHUTDOWN SYSLOG+WALL+EXEC
NOTIFYFLAG REPLBATT SYSLOG+WALL
NOTIFYFLAG NOCOMM SYSLOG+WALL+EXEC
NOTIFYFLAG NOPARENT SYSLOG+WALL
RBWARNTIME 43200
NOCOMMWARNTIME 600
FINALDELAY 5
upssched.conf- Scheduler rules for conditional shutdowns.
1
2
3
4
5
6
7
8
9
10
CMDSCRIPT /etc/nut/upssched-cmd.sh
PIPEFN /var/run/nut/upssched.pipe
LOCKFN /var/run/nut/upssched.lock
AT COMMBAD * EXECUTE oncommbad
AT COMMOK * EXECUTE oncommok
AT ONLINE * EXECUTE online
AT ONBATT * START-TIMER onbatt 5
AT ONBATT * START-TIMER shutdown30 60
AT LOWBATT * EXECUTE lowbatt
AT FSD * EXECUTE forcedshutdown
upssched-cmd.sh- Script triggered by upssched events.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#!/bin/bash
LOGFILE="/var/log/upssched.log"
UPS="apc-modem@ip.address.of.nut.server"
echo "[$(date)] upssched-cmd triggered: $1" >> "$LOGFILE"
case $1 in
shutdown30)
charge=$(upsc $UPS battery.charge 2>/dev/null)
echo "[$(date)] Battery charge: $charge%" >> "$LOGFILE"
if [ -n "$charge" ] && [ "$charge" -le 30 ]; then
echo "[$(date)] Battery at or below 30%. Initiating shutdown." >> "$LOGFILE"
/usr/local/sbin/pve-shutdown.sh
else
echo "[$(date)] Battery still above 30%. Shutdown canceled." >> "$LOGFILE"
fi
;;
lowbatt)
echo "[$(date)] LOWBATT received. Forcing shutdown." >> "$LOGFILE"
/usr/local/sbin/pve-shutdown.sh
;;
forcedshutdown)
echo "[$(date)] FSD received. Forcing shutdown." >> "$LOGFILE"
/usr/local/sbin/pve-shutdown.sh
;;
*)
echo "[$(date)] Unknown event: $1" >> "$LOGFILE"
;;
esac
pve-shutdown.sh- Graceful shutdown logic for VMs, CTs, and power state checks.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
#!/bin/bash
# Place the script in /usr/local/sbin/
LOGFILE="/var/log/pve-shutdown.log"
UPS_NAME="apc-modem@ip.address.of.nut.server"
GRACE_PERIOD=180
CHECK_INTERVAL=10
echo "[$(date)] Starting Proxmox shutdown procedure via NUT" >> "$LOGFILE"
echo "[$(date)] Shutting down all VMs..." >> "$LOGFILE"
for vmid in $(qm list | awk 'NR>1 {print $1}'); do
echo "[$(date)] Shutting down VM ID $vmid" >> "$LOGFILE"
qm shutdown $vmid &
done
echo "[$(date)] Shutting down all LXC containers..." >> "$LOGFILE"
for ctid in $(pct list | awk 'NR>1 {print $1}'); do
echo "[$(date)] Shutting down CT ID $ctid" >> "$LOGFILE"
pct shutdown $ctid &
done
echo "[$(date)] Waiting for all guests to shut down..." >> "$LOGFILE"
timeout=300
while [ $timeout -gt 0 ]; do
running_vms=$(qm list | awk 'NR>1 && $3 == "running" {print $1}')
running_cts=$(pct list | awk 'NR>1 && $2 == "running" {print $1}')
if [ -z "$running_vms" ] && [ -z "$running_cts" ]; then
echo "[$(date)] All guests shut down successfully." >> "$LOGFILE"
break
fi
echo "[$(date)] Still shutting down... ($timeout seconds left)" >> "$LOGFILE"
sleep 10
timeout=$((timeout - 10))
done
echo "[$(date)] Entering $GRACE_PERIOD second grace period before shutdown." >> "$LOGFILE"
remaining=$GRACE_PERIOD
while [ $remaining -gt 0 ]; do
status=$(upsc "$UPS_NAME" ups.status 2>/dev/null)
if echo "$status" | grep -q "OL"; then
echo "[$(date)] Power returned. Canceling shutdown." >> "$LOGFILE"
for vmid in $(qm list | awk 'NR>1 {print $1}'); do
echo "[$(date)] Restarting VM ID $vmid" >> "$LOGFILE"
qm start $vmid
done
for ctid in $(pct list | awk 'NR>1 {print $1}'); do
echo "[$(date)] Restarting CT ID $ctid" >> "$LOGFILE"
pct start $ctid
done
echo "[$(date)] Shutdown canceled. System remains up." >> "$LOGFILE"
exit 0
fi
echo "[$(date)] Power still out. ($remaining seconds left)" >> "$LOGFILE"
sleep $CHECK_INTERVAL
remaining=$((remaining - CHECK_INTERVAL))
done
echo "[$(date)] Grace period over. Power not restored. Proceeding with shutdown." >> "$LOGFILE"
shutdown -h now
🧰 Setup Instructions
1. Copy the Config and Scripts
1
2
3
4
5
cp upsmon.conf /etc/nut/upsmon.conf
cp upssched.conf /etc/nut/upssched.conf
cp upssched-cmd.sh /etc/nut/upssched-cmd.sh
cp pve-shutdown.sh /usr/local/sbin/pve-shutdown.sh
chmod +x /etc/nut/upssched-cmd.sh /usr/local/sbin/pve-shutdown.sh
2. Make Sure upsmon Runs as Root
Ensure upsmon.conf contains:
1
RUN_AS_USER root
3. Update UPS Server Address
Replace ip.address.of.nut.server in all files with the actual IP or hostname of your NUT server.
⚙️ Configuration Overview
upsmon.conf Highlights
- Monitors remote UPS via
MONITORline. - Triggers
/usr/sbin/upsschedfor advanced scheduling. - Executes shutdown via
pve-shutdown.shwhen needed. - Notifies system users via
WALL, logs events, and runs scripts.
upssched.conf Behavior
Schedules actions on power events:
- Starts a 60-second timer after
ONBATTto check battery level. - Executes shutdown if battery is at or below 30%.
- Executes immediate shutdown on
LOWBATTorFSD.
upssched-cmd.sh Logic
- Logs all events.
- Executes
pve-shutdown.shifbattery.charge <= 30. - Cancels shutdown if power returns.
pve-shutdown.sh
- Shuts down all VMs and containers.
- Waits up to 5 minutes for graceful shutdown.
- Waits a configurable grace period (default: 180 seconds).
- Cancels shutdown and restarts services if power returns.
- Executes
shutdown -h nowif not.
🔄 Testing the Setup
- Simulate a power failure by disconnecting the UPS from wall power.
- Observe
/var/log/upssched.logand/var/log/pve-shutdown.log. - Confirm proper shutdown behavior and power restoration handling.
✅ Final Notes
- Ensure
upscis installed for UPS status checks. - Adjust timing and thresholds as needed in the script.
- Test in a safe environment before production rollout.
