CyberPower CST1500UC2 + ESXi: Complete NUT, Docker & Telegram Setup Guide

CyberPower CST1500UC2 + ESXi: Complete NUT, Docker & Telegram Setup Guide

Jul 03, 2026 By Roman Yadzhak 12 min read 109 views

This guide explains how to connect a CyberPower CST1500UC2 UPS to a Linux VM running on VMware ESXi, monitor it with NUT in Docker, send Telegram alerts, and automatically shut down ESXi virtual machines safely during a power outage.


  1. Create a VM in ESXi

Create a small Linux VM on ESXi.

Recommended VM resources:

1 vCPU
2 GB RAM
20 GB disk
Ubuntu or Debian

After creating the VM:

Open VM settings.
Add USB Controller.
Add USB Device.
Select the CyberPower UPS USB device.
Start the VM.

Check if the UPS is detected:

lsusb


  1. Install Docker

Install Docker and Docker Compose plugin:

sudo apt update sudo apt install docker.io docker-compose-plugin -y

Add your user to the Docker group:

sudo usermod -aG docker $USER newgrp docker

Check Docker:

docker version


  1. Create NUT Docker Directory

Create the working directory:

sudo mkdir -p /opt/ups-nut cd /opt/ups-nut

Create the Docker Compose file:

nano docker-compose.yml

Copy this code:

services:
nut:
image: instantlinux/nut-upsd:latest
container_name: nut-ups
restart: unless-stopped
privileged: true
environment:
- API_USER=upsmon
      - API_PASSWORD=StrongPassword123
      - DRIVER=usbhid-ups
      - NAME=cyberpower
      - PORT=auto
      - DESCRIPTION=CyberPower CST1500UC2
    ports:
      - "3493:3493"
    devices:
      - /dev/bus/usb:/dev/bus/usb
    volumes:
      - /dev/bus/usb:/dev/bus/usb
  nut-web:
    image: ghcr.io/superioone/nut_webgui:latest
    container_name: nut-web
    restart: unless-stopped
    ports:      - "9000:9000"
    environment:
      - UPSD_ADDR=nut
      - UPSD_PORT=3493
      - UPSD_USER=upsmon
      - UPSD_PASS=StrongPassword123
    depends_on:
      - nut

Start the container:

docker compose up -dhttp://<your IP srv>:9000/

Check container logs:

docker logs -f nut-ups


  1. Check UPS Status

Show all UPS information:

docker exec -it nut-ups upsc cyberpower@localhost

Check only UPS status:

docker exec -it nut-ups upsc cyberpower@localhost ups.status

Common status values:

OL = On Line / utility power is available
OB = On Battery / power is lost
LB = Low Battery


  1. Create Telegram Notification Script

Create the script:

sudo nano /usr/local/bin/ups-telegram.sh

Copy this code:

#!/bin/bash BOT_TOKEN=“YOUR_BOT_TOKEN” CHAT_ID=“YOUR_CHAT_ID” STATUS=$(docker exec nut-ups upsc cyberpower@localhost ups.status 2>/dev/null) BATTERY=$(docker exec nut-ups upsc cyberpower@localhost battery.charge 2>/dev/null) RUNTIME=$(docker exec nut-ups upsc cyberpower@localhost battery.runtime 2>/dev/null) LOAD=$(docker exec nut-ups upsc cyberpower@localhost ups.load 2>/dev/null) INPUT=$(docker exec nut-ups upsc cyberpower@localhost input.voltage 2>/dev/null) MESSAGE=”$1 Host: $(hostname) UPS status: $STATUS Battery: $BATTERY% Runtime: $RUNTIME sec Load: $LOAD% Input voltage: $INPUT V”curl -s -X POST "https://api.telegram.org/bot${BOT_TOKEN}/sendMessage" \  -d chat_id="${CHAT_ID}" \  -d text="$MESSAGE" > /dev/null

Make it executable:

sudo chmod +x /usr/local/bin/ups-telegram.sh

Test Telegram message:

/usr/local/bin/ups-telegram.sh “✅ UPS Telegram test”


  1. Create UPS Monitor Script

This script checks UPS status every 30 seconds. If power is lost, it sends a Telegram alert. If battery or runtime becomes low, it connects to ESXi by SSH and starts the shutdown script.

Create the script:

sudo nano /usr/local/bin/ups-monitor.sh

Copy this code:

#!/bin/bash UPS="cyberpower@localhost" STATE_FILE="/tmp/ups_last_state" SHUTDOWN_FILE="/tmp/ups_shutdown_started" ESXI_HOST="<esxi ip>" ESXI_USER="root" BATTERY_LIMIT=30 RUNTIME_LIMIT=600 STATUS=$(docker exec nut-ups upsc "$UPS" ups.status 2>/dev/null) BATTERY=$(docker exec nut-ups upsc "$UPS" battery.charge 2>/dev/null) RUNTIME=$(docker exec nut-ups upsc "$UPS" battery.runtime 2>/dev/null) LAST_STATE=$(cat "$STATE_FILE" 2>/dev/null || echo "UNKNOWN") if [[ "$STATUS" == *"OB"* && "$LAST_STATE" != "OB" ]]; then /usr/local/bin/ups-telegram.sh \ "⚡ Power lost. UPS is on battery. Battery: ${BATTERY}%. Runtime: ${RUNTIME}s." echo "OB" > "$STATE_FILE" fi if [[ "$STATUS" == *"OL"* && "$LAST_STATE" != "OL" ]]; then /usr/local/bin/ups-telegram.sh \ "✅ Power restored. UPS is back online. Battery: ${BATTERY}%." echo "OL" > "$STATE_FILE" rm -f "$SHUTDOWN_FILE" fi if [[ "$STATUS" == *"OB"* ]]; then if [[ "$BATTERY" =~ ^[0-9]+$ && "$RUNTIME" =~ ^[0-9]+$ ]]; then if (( BATTERY <= BATTERY_LIMIT || RUNTIME <= RUNTIME_LIMIT )); then if [[ ! -f "$SHUTDOWN_FILE" ]]; then touch "$SHUTDOWN_FILE" /usr/local/bin/ups-telegram.sh \ "🚨 UPS battery low. Battery: ${BATTERY}%. Runtime: ${RUNTIME}s. Starting graceful shutdown of ESXi and VMs." if ssh \ -o BatchMode=yes \ -o ConnectTimeout=10 \ "${ESXI_USER}@${ESXI_HOST}" \ "nohup /bin/sh /vmfs/volumes/VMwarw-HHD-2TB/scripts/esxi-ups-shutdown.sh >>/vmfs/volumes/VMwarw-HHD-2TB/scripts/ups-shutdown.log 2>&1 </dev/null &" then /usr/local/bin/ups-telegram.sh \ "🖥️ ESXi shutdown command was successfully submitted." else rm -f "$SHUTDOWN_FILE" /usr/local/bin/ups-telegram.sh \ "❌ Failed to start ESXi shutdown. Check SSH access and the remote script path." fi fi fi else /usr/local/bin/ups-telegram.sh \ "⚠️ Invalid UPS data received. Battery: '${BATTERY}', runtime: '${RUNTIME}', status: '${STATUS}'." fi fi

Make it executable:

sudo chmod +x /usr/local/bin/ups-monitor.sh


  1. Create systemd Service

Create the service file:

sudo nano /etc/systemd/system/ups-monitor.service

Copy this code:

[Unit] Description=UPS Monitor Telegram Alerts After=docker.service Requires=docker.service
[Service] Type=simple ExecStart=/bin/bash -c ‘while true; do /usr/local/bin/ups-monitor.sh; sleep 30; done’ Restart=always
[Install] WantedBy=multi-user.target

Reload systemd:

sudo systemctl daemon-reload

Enable the service:

sudo systemctl enable ups-monitor

Start the service:

sudo systemctl start ups-monitor

Check service status:

sudo systemctl status ups-monitor


  1. Configure SSH Access to ESXi

Generate SSH key on the Linux UPS VM:

ssh-keygen -t ed25519

Copy the key to ESXi:

ssh-copy-id root@<your ip>

Test SSH login:

ssh root@<your ip> hostname


  1. Enable SSH on ESXi

In ESXi UI:

Go to Host.
Open Manage.
Open Services.
Start TSM-SSH.

Check authorized keys on ESXi:

cat /etc/ssh/keys-root/authorized_keys


  1. Create ESXi Shutdown Script

On ESXi, create the scripts directory:

mkdir -p /vmfs/volumes/VMwarw-HHD-2TB/scripts/

Create the shutdown script:

vi /vmfs/volumes/VMwarw-HHD-2TB/scripts/esxi-ups-shutdown.sh

Copy this code:


#!/bin/sh LOG="/vmfs/volumes/VMwarw-HHD-2TB/scripts/ups-shutdown.log" UPS_VM_NAME="heimdall" VM_SHUTDOWN_TIMEOUT=180 UPS_VM_SHUTDOWN_TIMEOUT=60 CHECK_INTERVAL=10 log_message() { echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" >> "$LOG" } get_vm_name() { vim-cmd vmsvc/get.summary "$1" 2>/dev/null | grep "name =" | head -1 | sed 's/.*name = "\(.*\)",/\1/' } get_vm_state() { vim-cmd vmsvc/power.getstate "$1" 2>/dev/null | tail -1 } is_ups_vm() { VM_NAME_TO_CHECK="$1" [ "$VM_NAME_TO_CHECK" = "$UPS_VM_NAME" ] } shutdown_vm() { VM_ID="$1" VM_NAME="$2" log_message "Requesting graceful shutdown: VM $VM_ID - $VM_NAME" vim-cmd vmsvc/power.shutdown "$VM_ID" >> "$LOG" 2>&1 if [ $? -ne 0 ]; then log_message "WARNING: Graceful shutdown request failed for VM $VM_ID - $VM_NAME" fi } force_power_off_vm() { VM_ID="$1" VM_NAME="$2" log_message "Force powering off: VM $VM_ID - $VM_NAME" vim-cmd vmsvc/power.off "$VM_ID" >> "$LOG" 2>&1 if [ $? -ne 0 ]; then log_message "ERROR: Force power-off failed for VM $VM_ID - $VM_NAME" fi } wait_for_regular_vms() { ELAPSED=0 while [ "$ELAPSED" -lt "$VM_SHUTDOWN_TIMEOUT" ]; do POWERED_ON_COUNT=0 for VM_ID in $(vim-cmd vmsvc/getallvms | awk 'NR > 1 && $1 ~ /^[0-9]+$/ {print $1}'); do VM_NAME=$(get_vm_name "$VM_ID") VM_STATE=$(get_vm_state "$VM_ID") if is_ups_vm "$VM_NAME"; then continue fi if [ "$VM_STATE" = "Powered on" ]; then POWERED_ON_COUNT=$((POWERED_ON_COUNT + 1)) fi done if [ "$POWERED_ON_COUNT" -eq 0 ]; then log_message "All regular VMs are powered off." return 0 fi log_message "Waiting for $POWERED_ON_COUNT VM(s) to shut down. Elapsed: ${ELAPSED}s." sleep "$CHECK_INTERVAL" ELAPSED=$((ELAPSED + CHECK_INTERVAL)) done log_message "Regular VM shutdown timeout reached after ${VM_SHUTDOWN_TIMEOUT}s." return 1 } wait_for_vm_shutdown() { VM_ID="$1" VM_NAME="$2" TIMEOUT="$3" ELAPSED=0 while [ "$ELAPSED" -lt "$TIMEOUT" ]; do VM_STATE=$(get_vm_state "$VM_ID") if [ "$VM_STATE" != "Powered on" ]; then log_message "VM $VM_ID - $VM_NAME is powered off." return 0 fi sleep "$CHECK_INTERVAL" ELAPSED=$((ELAPSED + CHECK_INTERVAL)) done return 1 } log_message "============================================================" log_message "UPS-triggered ESXi shutdown started." log_message "UPS monitor VM configured as: $UPS_VM_NAME" # Request graceful shutdown for all regular VMs. for VM_ID in $(vim-cmd vmsvc/getallvms | awk 'NR > 1 && $1 ~ /^[0-9]+$/ {print $1}'); do VM_NAME=$(get_vm_name "$VM_ID") VM_STATE=$(get_vm_state "$VM_ID") log_message "Detected VM $VM_ID - $VM_NAME - $VM_STATE" if is_ups_vm "$VM_NAME"; then log_message "Skipping UPS monitor VM until last: $VM_NAME" continue fi if [ "$VM_STATE" = "Powered on" ]; then shutdown_vm "$VM_ID" "$VM_NAME" else log_message "VM already powered off: $VM_ID - $VM_NAME" fi done # Wait for regular VMs to shut down gracefully. wait_for_regular_vms # Force power off any regular VM that is still running. for VM_ID in $(vim-cmd vmsvc/getallvms | awk 'NR > 1 && $1 ~ /^[0-9]+$/ {print $1}'); do VM_NAME=$(get_vm_name "$VM_ID") VM_STATE=$(get_vm_state "$VM_ID") if is_ups_vm "$VM_NAME"; then continue fi if [ "$VM_STATE" = "Powered on" ]; then force_power_off_vm "$VM_ID" "$VM_NAME" fi done # Shut down the UPS monitoring VM last. UPS_VM_FOUND=0 for VM_ID in $(vim-cmd vmsvc/getallvms | awk 'NR > 1 && $1 ~ /^[0-9]+$/ {print $1}'); do VM_NAME=$(get_vm_name "$VM_ID") if ! is_ups_vm "$VM_NAME"; then continue fi UPS_VM_FOUND=1 VM_STATE=$(get_vm_state "$VM_ID") log_message "UPS monitor VM found: VM $VM_ID - $VM_NAME - $VM_STATE" if [ "$VM_STATE" = "Powered on" ]; then shutdown_vm "$VM_ID" "$VM_NAME" if wait_for_vm_shutdown "$VM_ID" "$VM_NAME" "$UPS_VM_SHUTDOWN_TIMEOUT"; then log_message "UPS monitor VM shut down gracefully." else log_message "UPS monitor VM did not shut down within ${UPS_VM_SHUTDOWN_TIMEOUT}s." force_power_off_vm "$VM_ID" "$VM_NAME" fi else log_message "UPS monitor VM is already powered off." fi break done if [ "$UPS_VM_FOUND" -eq 0 ]; then log_message "WARNING: UPS monitor VM '$UPS_VM_NAME' was not found." fi log_message "Entering ESXi maintenance mode." vim-cmd hostsvc/maintenance_mode_enter >> "$LOG" 2>&1 if [ $? -ne 0 ]; then log_message "WARNING: ESXi could not enter maintenance mode. Continuing with host shutdown." else log_message "ESXi entered maintenance mode." fi log_message "Powering off ESXi host in 10 seconds." sync sleep 10 log_message "Executing ESXi host poweroff." sync /sbin/poweroff

Make the script executable:

chmod +x /vmfs/volumes/VMwarw-HHD-2TB/scripts/esxi-ups-shutdown.sh


  1. How the Shutdown Logic Works

When power is normal:

UPS status: OL

The system does nothing and continues monitoring.

When power is lost:

UPS status: OB

Telegram sends:

⚡ Power lost. UPS is on battery.

When battery is low or runtime is low:

Battery <= 30%
Runtime <= 600 seconds

Telegram sends:

🚨 UPS battery low. Starting graceful shutdown of ESXi and VMs.

Then the Linux VM connects to ESXi and runs:

/vmfs/volumes/VMwarw-HHD-2TB/scripts/esxi-ups-shutdown.sh

The ESXi script:

Shuts down all VMs gracefully.
Skips the UPS monitor VM until the end.
Waits 180 seconds.
Forces off any VM still running.
Shuts down the UPS monitor VM last.
Powers off the ESXi host.


  1. Useful Commands

Check UPS status:

docker exec -it nut-ups upsc cyberpower@localhost ups.status

Show all UPS values:

docker exec -it nut-ups upsc cyberpower@localhost

Check NUT container logs:

docker logs -f nut-ups

Restart UPS monitor service:

sudo systemctl restart ups-monitor

Check UPS monitor service:

sudo systemctl status ups-monitor

Check ESXi shutdown log:

cat /vmfs/volumes/VMwarw-HHD-2TB/scripts/ups-shutdown.log


  1. Result

After this setup, the CyberPower CST1500UC2 UPS will automatically protect your ESXi environment.

You will have:

UPS monitoring through USB passthrough
NUT running inside Docker
Telegram alerts for power loss and power restore
Battery and runtime monitoring
Automatic ESXi shutdown
Graceful VM shutdown before host poweroff
UPS monitor VM shutdown last


#CyberPower CST1500UC2 #CyberPower UPS ESXi #NUT Docker 

#VMware ESXi UPS #UPS Monitoring #Telegram UPS Alerts 

#Automatic ESXi Shutdown #Network UPS Tools #USB Passthrough ESXi 

#Docker UPS Monitoring

Need help building this platform?

Schedule a consultation with Yadzhak Techmotion and turn this article into a production-ready implementation.

Schedule consultation

← Back to blog