Automation

Essential Bash Scripts for Proxmox VE Administration

Ready-to-use bash scripts for Proxmox VE: bulk VM operations, backup cleanup, resource reporting, snapshot auditing, and cluster health checks.

ProxmoxR app icon

Managing Proxmox? Try ProxmoxR

Monitor and control your VMs & containers from your phone.

Try Free

Not every Proxmox automation task requires Terraform or Ansible. For quick, targeted operations — bulk starting VMs, cleaning old backups, or generating a resource report — a well-written bash script is often the most practical solution. This guide provides ready-to-use scripts that you can drop onto your Proxmox node and run immediately.

1. Bulk VM Operations

Start, stop, or snapshot multiple VMs at once, filtered by name pattern or VMID range:

#!/bin/bash
# bulk-vm-ops.sh — Perform actions on multiple VMs
# Usage: ./bulk-vm-ops.sh [start|stop|shutdown|snapshot] [pattern]

ACTION="${1:-status}"
PATTERN="${2:-.*}"

echo "=== Bulk VM Operation: $ACTION (filter: $PATTERN) ==="
echo "Timestamp: $(date '+%Y-%m-%d %H:%M:%S')"
echo ""

for VMID in $(qm list | awk 'NR>1 {print $1}'); do
    VM_NAME=$(qm config "$VMID" | grep '^name:' | awk '{print $2}')

    # Skip VMs that don't match the pattern
    echo "$VM_NAME" | grep -qE "$PATTERN" || continue

    case "$ACTION" in
        start)
            echo "Starting VM $VMID ($VM_NAME)..."
            qm start "$VMID" 2>&1
            ;;
        stop)
            echo "Stopping VM $VMID ($VM_NAME)..."
            qm stop "$VMID" 2>&1
            ;;
        shutdown)
            echo "Graceful shutdown VM $VMID ($VM_NAME)..."
            qm shutdown "$VMID" --timeout 120 2>&1
            ;;
        snapshot)
            SNAP_NAME="bulk_$(date '+%Y%m%d_%H%M%S')"
            echo "Snapshotting VM $VMID ($VM_NAME) as $SNAP_NAME..."
            qm snapshot "$VMID" "$SNAP_NAME" --description "Bulk snapshot" 2>&1
            ;;
        status)
            STATUS=$(qm status "$VMID" | awk '{print $2}')
            printf "  VM %-6s %-25s %s\n" "$VMID" "$VM_NAME" "$STATUS"
            ;;
    esac
done

echo ""
echo "Done."

2. Backup Cleanup Script

Automatically remove old backup files while keeping a configurable number of recent backups per VM:

#!/bin/bash
# backup-cleanup.sh — Remove old vzdump backups, keeping N most recent per VM
# Usage: ./backup-cleanup.sh [keep_count] [backup_dir]

KEEP=${1:-3}
BACKUP_DIR="${2:-/var/lib/vz/dump}"
DRY_RUN="${3:-false}"

echo "=== Backup Cleanup ==="
echo "Directory: $BACKUP_DIR"
echo "Keeping: $KEEP most recent per VM"
echo ""

TOTAL_FREED=0

# Find all unique VMIDs in backup filenames
for VMID in $(ls "$BACKUP_DIR"/vzdump-*-*.vma* 2>/dev/null | \
    sed -E 's/.*vzdump-(qemu|lxc)-([0-9]+)-.*/\2/' | sort -u); do

    # List backups for this VMID, newest first
    BACKUPS=($(ls -t "$BACKUP_DIR"/vzdump-*-"${VMID}"-*.vma* 2>/dev/null))
    TOTAL=${#BACKUPS[@]}

    if [ "$TOTAL" -le "$KEEP" ]; then
        echo "VM $VMID: $TOTAL backups (keeping all)"
        continue
    fi

    DELETE_COUNT=$((TOTAL - KEEP))
    echo "VM $VMID: $TOTAL backups, removing $DELETE_COUNT oldest"

    # Remove the oldest backups (beyond the keep count)
    for BACKUP_FILE in "${BACKUPS[@]:$KEEP}"; do
        FILE_SIZE=$(stat -c%s "$BACKUP_FILE" 2>/dev/null || echo 0)
        TOTAL_FREED=$((TOTAL_FREED + FILE_SIZE))

        if [ "$DRY_RUN" = "true" ]; then
            echo "  [DRY RUN] Would remove: $(basename "$BACKUP_FILE")"
        else
            echo "  Removing: $(basename "$BACKUP_FILE")"
            rm -f "$BACKUP_FILE"
            # Also remove associated .log and .notes files
            rm -f "${BACKUP_FILE%.vma*}.log"
            rm -f "${BACKUP_FILE%.vma*}.notes"
        fi
    done
done

echo ""
echo "Space freed: $((TOTAL_FREED / 1024 / 1024)) MB"

3. Resource Report Script

Generate a summary of CPU, memory, and disk usage across all VMs and containers:

#!/bin/bash
# resource-report.sh — Generate a resource utilization report

echo "=========================================="
echo "  Proxmox Resource Report"
echo "  Host: $(hostname)"
echo "  Date: $(date '+%Y-%m-%d %H:%M:%S')"
echo "=========================================="
echo ""

# Host resources
echo "--- Host Resources ---"
echo "CPU Cores: $(nproc)"
echo "Load Average: $(uptime | awk -F'load average:' '{print $2}')"
free -h | awk '/^Mem:/ {printf "Memory: %s used / %s total (%s free)\n", $3, $2, $4}'
echo ""

# VM summary
echo "--- Virtual Machines ---"
printf "%-6s %-20s %-8s %-6s %-10s %-8s\n" "VMID" "NAME" "STATUS" "CORES" "MEMORY" "DISK"
echo "--------------------------------------------------------------"

for VMID in $(qm list | awk 'NR>1 {print $1}'); do
    CONFIG=$(qm config "$VMID" 2>/dev/null)
    NAME=$(echo "$CONFIG" | grep '^name:' | awk '{print $2}')
    CORES=$(echo "$CONFIG" | grep '^cores:' | awk '{print $2}')
    MEMORY=$(echo "$CONFIG" | grep '^memory:' | awk '{print $2}')
    STATUS=$(qm status "$VMID" 2>/dev/null | awk '{print $2}')

    # Get primary disk size
    DISK=$(echo "$CONFIG" | grep -E '^(scsi|virtio|ide|sata)0:' | \
        grep -oP 'size=\K[^,]+' | head -1)

    printf "%-6s %-20s %-8s %-6s %-10s %-8s\n" \
        "$VMID" "${NAME:--}" "$STATUS" "${CORES:-?}" "${MEMORY:-?}MB" "${DISK:--}"
done

echo ""

# Container summary
echo "--- LXC Containers ---"
printf "%-6s %-20s %-8s %-6s %-10s\n" "CTID" "NAME" "STATUS" "CORES" "MEMORY"
echo "----------------------------------------------"

for CTID in $(pct list | awk 'NR>1 {print $1}'); do
    CONFIG=$(pct config "$CTID" 2>/dev/null)
    NAME=$(echo "$CONFIG" | grep '^hostname:' | awk '{print $2}')
    CORES=$(echo "$CONFIG" | grep '^cores:' | awk '{print $2}')
    MEMORY=$(echo "$CONFIG" | grep '^memory:' | awk '{print $2}')
    STATUS=$(pct status "$CTID" 2>/dev/null | awk '{print $2}')

    printf "%-6s %-20s %-8s %-6s %-10s\n" \
        "$CTID" "${NAME:--}" "$STATUS" "${CORES:-?}" "${MEMORY:-?}MB"
done

echo ""

# Storage summary
echo "--- Storage ---"
pvesm status 2>/dev/null | awk 'NR==1 || NR>1 {printf "%-15s %-10s %-10s %-10s %s\n", $1, $2, $3, $4, $5}'

4. Snapshot Audit Script

Find stale snapshots that are consuming disk space:

#!/bin/bash
# snapshot-audit.sh — Find old snapshots across all VMs and containers

MAX_AGE_DAYS=${1:-7}
echo "=== Snapshot Audit (older than $MAX_AGE_DAYS days) ==="
echo ""

FOUND=0

# Audit VM snapshots
for VMID in $(qm list | awk 'NR>1 {print $1}'); do
    VM_NAME=$(qm config "$VMID" | grep '^name:' | awk '{print $2}')
    SNAPSHOTS=$(qm listsnapshot "$VMID" 2>/dev/null | grep -v "current" | grep -v "^\`" | awk '{print $2}')

    for SNAP in $SNAPSHOTS; do
        [ -z "$SNAP" ] && continue
        [ "$SNAP" = "current" ] && continue
        FOUND=$((FOUND + 1))
        echo "  VM $VMID ($VM_NAME): snapshot '$SNAP'"
    done
done

# Audit container snapshots
for CTID in $(pct list 2>/dev/null | awk 'NR>1 {print $1}'); do
    CT_NAME=$(pct config "$CTID" | grep '^hostname:' | awk '{print $2}')
    SNAPSHOTS=$(pct listsnapshot "$CTID" 2>/dev/null | grep -v "current" | awk '{print $2}')

    for SNAP in $SNAPSHOTS; do
        [ -z "$SNAP" ] && continue
        [ "$SNAP" = "current" ] && continue
        FOUND=$((FOUND + 1))
        echo "  CT $CTID ($CT_NAME): snapshot '$SNAP'"
    done
done

echo ""
echo "Total snapshots found: $FOUND"
[ "$FOUND" -gt 0 ] && echo "Review and remove stale snapshots to reclaim disk space."

5. Cluster Health Check

A comprehensive health check script for Proxmox clusters:

#!/bin/bash
# cluster-health.sh — Quick cluster health overview

echo "=== Cluster Health Check ==="
echo "Date: $(date '+%Y-%m-%d %H:%M:%S')"
echo ""

# Cluster status
echo "--- Cluster Status ---"
pvecm status 2>/dev/null | grep -E "(Cluster|Quorum|Node|Expected|Total)"
echo ""

# Node status
echo "--- Node Status ---"
pvecm nodes 2>/dev/null
echo ""

# Check for failed services
echo "--- Service Health ---"
for SVC in pvedaemon pveproxy pvestatd corosync pve-cluster; do
    STATUS=$(systemctl is-active "$SVC" 2>/dev/null)
    printf "  %-20s %s\n" "$SVC" "$STATUS"
done
echo ""

# HA status
echo "--- HA Status ---"
ha-manager status 2>/dev/null || echo "  HA not configured"
echo ""

# Storage health
echo "--- Storage Health ---"
pvesm status 2>/dev/null | awk '{printf "  %-15s %-10s %-8s\n", $1, $2, ($NF=="active" ? "OK" : $NF)}'
echo ""

# Ceph status (if applicable)
if command -v ceph &>/dev/null; then
    echo "--- Ceph Status ---"
    ceph health 2>/dev/null
    echo ""
fi

echo "=== Check Complete ==="

Installation and Scheduling

# Copy scripts to the Proxmox node
scp *.sh root@192.168.1.100:/usr/local/bin/

# Make them executable
chmod +x /usr/local/bin/*.sh

# Schedule the backup cleanup weekly via cron
echo "0 3 * * 0 /usr/local/bin/backup-cleanup.sh 3 /var/lib/vz/dump >> /var/log/backup-cleanup.log 2>&1" | crontab -

# Run the health check on demand
/usr/local/bin/cluster-health.sh

These scripts give you direct, no-dependency automation for everyday Proxmox tasks. For a more visual overview of your cluster health and VM status on the go, ProxmoxR complements these command-line tools by putting key metrics on your phone. Together, scripts and mobile monitoring cover both scheduled automation and real-time visibility.

Take Proxmox management mobile

All the features discussed in this guide — accessible from your phone with ProxmoxR. Real-time monitoring, power control, firewall management, and more.

ProxmoxR

Manage Proxmox from your phone

Monitor, control, and manage your clusters on the go.

Free 7-day trial · No credit card required