
Proxmox VM Auto-Upgrade Script (qm + vzdump) with Network Tests and Auto-Restore
This article describes a simple (but effective) automation script for Proxmox VE that:
- creates a backup of a VM (optional),
- starts the VM if it was originally stopped,
- runs apt upgrade + reboot inside the VM,
- performs network tests before and after the update,
- restores from backup automatically if tests fail (optional),
- shuts down the VM again if it was originally stopped.
Where to download the script
You can download the script directly from my website:
soban.pl/bash/upgrade_proxmox_qm.sh
Required folders
Before you run anything, create two folders for scripts and logs:
|
1 |
mkdir -p /root/automate_scripts /root/logs |
Install the script
Download the file into /root/automate_scripts/ and make it executable:
|
1 2 3 |
cd /root/automate_scripts curl -fsSL -o upgrade_proxmox_qm.sh "https://soban.pl/bash/upgrade_proxmox_qm.sh" chmod +x /root/automate_scripts/upgrade_proxmox_qm.sh |
How it works (high level)
- Backup (vzdump snapshot + zstd) is executed first (if enabled).
- If the VM was stopped, the script starts it and waits WAIT_TIME seconds.
- Runs network tests BEFORE update:
- Executes apt update/upgrade/dist-upgrade/autoremove/clean and finally reboots the VM.
- Waits again (WAIT_TIME) and runs the same tests AFTER update.
- If tests fail and a backup exists, it performs qmrestore automatically.
- If the VM was originally stopped, it shuts down the VM at the end.
Usage
The script expects exactly two arguments:
VMID– the Proxmox VM IDTIME_SEC– how long to wait after start/reboot (in seconds)
|
1 |
/root/automate_scripts/upgrade_proxmox_qm.sh 901 500 |
Tip: choose a WAIT_TIME that matches your VM boot time and apt upgrade duration. Typical values are 300 to 1000 seconds depending on the VM.
Full script (for reference)
If you prefer to keep everything in one place, below is the full script exactly as used in this setup:
|
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 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 |
#!/bin/bash WAIT_TIME=$2 # wait time for VM start/restart (in seconds) DO_BACKUP="y" # 'y' - create backup, 'n' - skip backup DO_UPDATE="y" # 'y' - run system update, 'n' - skip update echo "---------------START---------------" date echo "-----------------------------------" # Argument check if [ "$#" -ne 2 ]; then echo "Usage: $0 <VMID> <TIME_SEC>" exit 1 fi VMID="$1" TARGET_HOSTNAME=$(/usr/sbin/qm list | grep -w "$VMID" | awk '{print $2}') VM_STATUS=$(/usr/sbin/qm status "$VMID" | awk '{print $2}') HOST_MACHINE=$(hostname) WAS_STOPPED=0 if [ "$VM_STATUS" = "stopped" ]; then WAS_STOPPED=1 else WAS_STOPPED=0 fi # Create backup if DO_BACKUP is set to 'y' backup_result="Backup not attempted" if [ "$DO_BACKUP" = "y" ]; then echo "Creating backup for VM $VMID..." BACKUP_OUTPUT=$(/usr/bin/vzdump "$VMID" --storage local --mode snapshot --compress zstd) BACKUP_FILE=$(echo "$BACKUP_OUTPUT" | grep -oP 'vzdump-qemu-[^\s]+' | tr -d "'") if [ -z "$BACKUP_FILE" ]; then echo "Backup failed: No backup file created." backup_result="Backup created: NOK" exit 1 else echo "Backup created successfully: $BACKUP_FILE" backup_result="Backup created: OK" fi else echo "Backup not attempted (backup is disabled)." fi # Start VM if it was originally stopped if [ "$WAS_STOPPED" -eq 1 ]; then echo "Starting VM $VMID for update..." /usr/sbin/qm start "$VMID" echo "Waiting $WAIT_TIME seconds for VM to start..." sleep "$WAIT_TIME" fi # Test functions perform_ping_test() { source="$1" target="$2" if ssh root@"$source" "ping -c 3 -W 2 $target" >/dev/null 2>&1; then echo "Ping from $source to $target: OK" return 0 else echo "Ping from $source to $target: NOK" return 1 fi } test_curl() { source="$1" target="$2" if ssh root@"$source" "curl -s --head --connect-timeout 5 $target" >/dev/null 2>&1; then echo "Curl from $source to $target: OK" return 0 else echo "Curl from $source to $target: NOK" return 1 fi } perform_local_ping_test() { source="$1" target="$2" if ping -c 3 -W 2 "$target" >/dev/null 2>&1; then echo "Ping from $source to $target: OK" return 0 else echo "Ping from $source to $target: NOK" return 1 fi } DEFAULT_GW=$(/sbin/ip route | grep default | awk '{print $3}') perform_tests() { status=0 perform_local_ping_test "$HOST_MACHINE" "$TARGET_HOSTNAME" || status=1 perform_ping_test "$TARGET_HOSTNAME" "8.8.8.8" || status=1 perform_ping_test "$TARGET_HOSTNAME" "$DEFAULT_GW" || status=1 test_curl "$TARGET_HOSTNAME" "http://google.com" || status=1 return ${status:-0} } # General tests before update echo "--- General tests BEFORE update ---" if perform_tests; then echo "All network tests before update: OK" echo "$backup_result" if [ "$backup_result" == "Backup created: OK" ] || [ "$backup_result" == "Backup not attempted" ]; then echo "BEFORE status: OK" else echo "BEFORE status: NOK" fi else echo "Some network tests before update: NOK" echo "$backup_result" echo "BEFORE status: NOK" [ "$WAS_STOPPED" -eq 1 ] && /usr/sbin/qm shutdown "$VMID" exit 1 fi echo "-----------------------------------" # System update and reboot update_result="Upgrade system: NOK" if [ "$DO_UPDATE" = "y" ]; then echo "--- Updating VM $VMID ---" if ssh root@"$TARGET_HOSTNAME" "/usr/bin/apt update && \ /usr/bin/apt upgrade -y && \ /usr/bin/apt dist-upgrade -y && \ /usr/bin/apt autoremove -y && \ /usr/bin/apt autoclean && \ /usr/bin/apt clean && \ /usr/bin/apt --purge autoremove && \ /sbin/reboot"; then echo "---END Updating VM $VMID ---" echo "Upgrade system: OK" update_result="Upgrade system: OK" else echo "Upgrade system: NOK" update_result="Upgrade system: NOK" [ "$WAS_STOPPED" -eq 1 ] && /usr/sbin/qm shutdown "$VMID" echo "Update failed. Exiting." exit 1 fi fi # Wait for VM reboot if update was performed if [ "$DO_UPDATE" = "y" ]; then echo "Waiting $WAIT_TIME seconds for VM to reboot..." sleep "$WAIT_TIME" fi # Tests after reboot if update was performed if [ "$DO_UPDATE" = "y" ]; then echo "--- Network tests AFTER update ---" if perform_tests; then echo "All network tests after update: OK" echo "$update_result" echo "AFTER status: OK" else echo "Some network tests after update: NOK" echo "$update_result" echo "Attempting to restore from backup..." if [ "$DO_BACKUP" = "y" ]; then /usr/sbin/qm stop "$VMID" /usr/sbin/qmrestore "/var/lib/vz/dump/$BACKUP_FILE" "$VMID" --force if [ "$WAS_STOPPED" -eq 1 ]; then /usr/sbin/qm shutdown "$VMID" fi echo "Restore attempt finished. Please check VM status." echo "AFTER status: NOK" else echo "No backup available for restoration. The VM might not function properly after failed update." echo "AFTER status: NOK" fi fi echo "-----------------------------------" fi # Shut down VM if it was originally stopped if [ "$WAS_STOPPED" -eq 1 ]; then echo "Shutting down VM $VMID (originally stopped)." /usr/sbin/qm shutdown "$VMID" fi echo "---------------END---------------" date echo "-----------------------------------" |
Cron jobs (weekly schedule + per-VM logs)
Below is an example crontab that runs upgrades once a week (different VM each weekday) and writes separate logs to /root/logs/.
Edit root crontab:
|
1 |
crontab -e |
Paste rules like these (comments in English):
|
1 2 3 4 |
15 0 * * 1 /root/automate_scripts/upgrade_proxmox_qm.sh 901 500 > /root/logs/kali.log 2>&1 # Monday: upgrade Kali Linux (VMID 901) 15 0 * * 2 /root/automate_scripts/upgrade_proxmox_qm.sh 903 500 > /root/logs/proxmox-test-01.log 2>&1 # Tuesday: upgrade proxmox-test-01 (VMID 903) 15 0 * * 3 /root/automate_scripts/upgrade_proxmox_qm.sh 904 500 > /root/logs/ubuntu-lte.log 2>&1 # Wednesday: upgrade ubuntu-lte (VMID 904) 15 0 * * 4 /root/automate_scripts/upgrade_proxmox_qm.sh 905 1000 > /root/logs/backup-machine.log 2>&1 # Thursday: upgrade backup-machine (VMID 905) |
Quick checks / troubleshooting
- Make sure root can SSH into the VM by hostname (the script uses
ssh root@<vm-hostname>). - Make sure DNS (or /etc/hosts) resolves the VM hostname correctly from the Proxmox host.
- If apt needs user interaction, ensure your VMs are set up for non-interactive upgrades (or pin/hold packages that prompt).
That’s it. Drop the script into /root/automate_scripts/, send logs into /root/logs/, and your weekly VM maintenance becomes mostly fire-and-forget.