Smart Proxmox Snapshots: Secure & Automated

A guide to setting up 'Least Privilege' API access and scripting intelligent snapshot retention for VMs and LXCs.

Smart Proxmox Snapshots: Secure & Automated
Belt and Suspenders ;-)

System administrators know the feeling all too well: an update is installed, a configuration is changed, and suddenly the service fails to start. In these moments, a recent snapshot is the most effective life insurance for any virtual environment. However, manual snapshots are often forgotten, and unmanaged automated snapshots can quickly fill up storage. A robust solution combines the principle of "Least Privilege" security with intelligent rotation logic, ensuring that daily, weekly, and monthly restore points are available without manual intervention. But don't forget: This is not a backup!

audio-thumbnail
Podcast Proxmox Snapshots
0:00
/201.130958

What is the advantage of this overkill

Most admins dump several jobs into /etc/crontab and manage timing by hard-coding the schedule there, pushing it to all VMs. That’s a mistake, because it wipes snapshots of powered-off VMs even when nothing has changed. It can also overwrite snapshots you created intentionally. Keep that in mind as you follow along with what I’m doing.

The Tooling: cv4pve-autosnap

To interact with the Proxmox API specifically for snapshot management, the tool cv4pve-autosnap is utilized. This standalone binary is capable of creating snapshots with retention labels (e.g., keep 3 snapshots) and handling the cleanup of older versions automatically.

Before proceeding, the binary must be downloaded and made executable on the Proxmox host. It is recommended to place the tool in a path accessible by the system, such as /usr/local/bin/. I used this little script:

LATEST_URL=$(curl -s https://api.github.com/repos/Corsinvest/cv4pve-autosnap/releases/latest \
| grep browser_download_url \
| grep linux-x64 \
| cut -d '"' -f 4)
wget "$LATEST_URL"

# Extract and make executable
unzip cv4pve-autosnap-linux-x64.zip
chmod +x cv4pve-autosnap

# Optional: Move to system path
sudo mv cv4pve-autosnap /usr/local/bin/

Security First: dedicated API Token and Permissions

Security is a primary concern when automating administrative tasks. Storing the root password in scripts is a significant security risk. Instead, a dedicated API Token is used. Following the "Least Privilege" principle, this token is granted only the permissions absolutely necessary to perform its task—nothing more.

For a snapshot tool to function correctly, access to the following resources is required:

  • VM.Audit: To identify existing VMs and check their status (running/stopped).
  • VM.Snapshot: To create, rollback, or delete snapshots.
  • Datastore.Audit: To monitor free space on storage backends (critical to prevent storage overflow).
  • Paths: Access is restricted to /vms (for virtual machines/containers) and /storage (for disk space checks).

The following setup script automates the creation of a restricted user role (SnapshotOnly), generates the API Token, and applies the specific Access Control Lists (ACLs). And since I am a lazy person, I created a tiny script, that I use on all my proxmox-servers:

#!/bin/bash
# M. Meister - Security Setup for Snapshot Automation

# --- CONFIGURATION ---
USER_ID="root@pam"
TOKEN_ID="snapshooter"
ROLE_NAME="SnapshotOnly"
# Rights: View VMs, Manage Snapshots, Check Storage Space
PRIVS="VM.Audit VM.Snapshot Datastore.Audit"

# Colors for output
GREEN='\033[0;32m'
RED='\033[0;31m'
NC='\033[0m' # No Color

echo -e "${GREEN}### Proxmox Snapshot-Token Setup ###${NC}"

# 1. Create or Update Role
if pveum role list | grep -q "$ROLE_NAME"; then
    echo -e "Role '$ROLE_NAME' already exists. Updating privileges..."
    pveum role modify "$ROLE_NAME" -privs "$PRIVS"
else
    echo -e "Creating new role '$ROLE_NAME'..."
    pveum role add "$ROLE_NAME" -privs "$PRIVS"
fi

# 2. Check if Token exists
FULL_TOKEN_ID="$USER_ID!$TOKEN_ID"

if pveum user token list "$USER_ID" | grep -q "$TOKEN_ID"; then
    echo -e "Token '$FULL_TOKEN_ID' already exists."
    echo -e "${RED}Note: The Secret (UUID) cannot be displayed again.${NC}"
    echo "If you lost the secret, please delete the token in the GUI and run this script again."
else
    echo -e "Creating Token '$FULL_TOKEN_ID'..."
    # Create token and capture output to extract the secret
    RESULT=$(pveum user token add "$USER_ID" "$TOKEN_ID" --privsep 1 --output-format json)
    SECRET=$(echo "$RESULT" | grep "value" | cut -d '"' -f 4)
    
    echo -e "${GREEN}-------------------------------------------------------------${NC}"
    echo -e "${GREEN}TOKEN CREATED! SAVE THIS SECURELY:${NC}"
    echo -e "Token ID: ${GREEN}$FULL_TOKEN_ID${NC}"
    echo -e "Secret:   ${GREEN}$SECRET${NC}"
    echo -e "${GREEN}-------------------------------------------------------------${NC}"
fi

# 3. Assign Permissions (ACLs)
echo "Setting permissions..."

# Rights for VMs (/vms)
echo " - Setting rights for /vms..."
pveum acl modify /vms -tokens "$FULL_TOKEN_ID" -role "$ROLE_NAME"

# Rights for Storage (/storage) - Crucial for Space-Check!
echo " - Setting rights for /storage..."
pveum acl modify /storage -tokens "$FULL_TOKEN_ID" -role "$ROLE_NAME"

# 4. Final Report
echo -e "${GREEN}### Done! ###${NC}"
echo "Current permissions for the token:"
pveum user permissions "$FULL_TOKEN_ID"

After executing this script, the Secret displayed in the output must be copied immediately, as it will be required for the automation script and is never shown again.

The "Brain": Intelligent Wrapper Script

Simply creating snapshots every day is not enough; an intelligent retention strategy is needed. A "Grandfather-Father-Son" rotation ensures that the system keeps, for example, 1 daily, 2 weekly, and 2 monthly snapshots.

The following wrapper script acts as the logic layer and only applies to running machines. Those that are not activated have most likely not changed. It determines the current date and decides whether a "daily", "weekly" (Sundays), or "monthly" (1st of the month) snapshot should be triggered. It then iterates through all running LXC containers and QEMU VMs to execute the backup.

Important: In the TOOL_ARGS variable, the placeholder YOUR_SECRET_UUID_HERE must be replaced with the secret generated in the previous step.

Script: proxmox_autosnap.sh

#!/bin/bash
# M. Meister - Smart Rotation Wrapper for cv4pve-autosnap

# --- Configuration ---
RETENTION_DAILY=1
RETENTION_WEEKLY=2
RETENTION_MONTHLY=2

# Path to the cv4pve-autosnap binary
CMD_TOOL="/usr/local/bin/cv4pve-autosnap"

# Login Data (Global)
# REPLACE 'YOUR_SECRET_UUID_HERE' with the secret from the setup script!
# format: user@realm!token_name=UUID
TOOL_ARGS="--host localhost --api-token root@pam!snapshooter=YOUR_SECRET_UUID_HERE"

# Timestamp for log output
log() {
    echo "[$(date +'%Y-%m-%d %H:%M:%S')] $1"
}

# Wrapper Function for Execution
run_autosnap() {
    local vmid=$1
    local label=$2
    local retention=$3
    
    log "Processing VM $vmid: Label='$label', Keep=$retention"
    
    # Syntax: cv4pve-autosnap [GLOBAL OPTIONS] snap [ACTION OPTIONS]
    output=$($CMD_TOOL $TOOL_ARGS --vmid "$vmid" snap --label "$label" --keep "$retention" 2>&1)
    status=$?
    
    if [ $status -eq 0 ]; then
        log "  -> Success."
    else
        log "  -> ERROR: $output"
    fi
}

# --- Main Logic ---
log "Starting cv4pve-autosnap Wrapper..."

# Date Check
IS_SUNDAY=0
[ "$(date +%u)" -eq 7 ] && IS_SUNDAY=1

IS_FIRST_OF_MONTH=0
[ "$(date +%d)" -eq 01 ] && IS_FIRST_OF_MONTH=1

# Logic: Which label applies today?
if [ "$IS_FIRST_OF_MONTH" -eq 1 ]; then
    CURRENT_LABEL="monthly"
    CURRENT_RETENTION="$RETENTION_MONTHLY"
    log "Mode: MONTHLY RUN (1st of Month)"
elif [ "$IS_SUNDAY" -eq 1 ]; then
    CURRENT_LABEL="weekly"
    CURRENT_RETENTION="$RETENTION_WEEKLY"
    log "Mode: WEEKLY RUN (Sunday)"
else
    CURRENT_LABEL="daily"
    CURRENT_RETENTION="$RETENTION_DAILY"
    log "Mode: DAILY RUN"
fi

# --- Process LXC Containers ---
# Only running containers
for vmid in $(pct list | awk 'NR>1 && /running/{print $1}'); do
    run_autosnap "$vmid" "$CURRENT_LABEL" "$CURRENT_RETENTION"
done

# --- Process QEMU VMs ---
# Only running VMs
for vmid in $(qm list | awk 'NR>1 && /running/{print $1}'); do
    run_autosnap "$vmid" "$CURRENT_LABEL" "$CURRENT_RETENTION"
done

log "All tasks completed."

Automation via Cron

To ensure the wrapper script runs automatically, a Cron job is configured. It is recommended to schedule the execution at a time when system load is low, for example, at 5:00 AM.

The crontab is edited using crontab -e, and the following line is added:

0 5 * * * /usr/local/bin/proxmox_autosnap.sh >> /var/log/proxmox_autosnap.log 2>&1

This configuration executes the script daily at 05:00 AM and redirects the output to a log file, allowing for easy verification of successful runs or troubleshooting of errors.

Conclusion

By combining the powerful Proxmox API, a dedicated "Least Privilege" token, and a smart logic script, a fully automated snapshot strategy is established. This setup ensures that systems are backed up with daily, weekly, and monthly granularity without manual effort or security compromises. The infrastructure is now resilient against failed updates or configuration errors, providing significant peace of mind for the administrator.