☁️ Proxmox Rclone Backup Manual


This manual provides a complete solution for automated backups of your Proxmox virtual machines and containers using Rclone with encrypted cloud storage via iDrive E2. Two scripts are used: one for the backup process and another for routing the upload traffic via a secondary WAN interface to relieve the primary uplink.


🗂️ Script 1: rclone_backup.sh

This script:

  • Finds all Proxmox backup files (*.zst) from the current month
  • Uploads them to iDrive E2 using rclone (with optional encryption)
  • Deletes remote backup folders older than 5 months
  • Uses screen for background execution and logs everything
  • Sends an email report with a list of uploaded files
#!/bin/bash
set -euo pipefail

# === Konfiguration ===
logfile="/var/log/rclone_backup.log"
temp_log="/tmp/rclone_run.log"
upload_list="/tmp/rclone_upload_list.txt"
email="[email protected]"
current_date=$(date +%Y-%m)
month_tag=$(date +%Y_%m)
screen_name="rclone-upload"
backup_source="/mnt/USB/dump/"
backup_target="idrive-enc:"
retention_limit=5  # Anzahl der Monate, die behalten werden

# === Logging mit Zeitstempel ===
log() {
  echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$logfile"
}

log "===== Backup-Start für $current_date ====="

# === Sicherstellen, dass Zielverzeichnis existiert ===
log "Stelle sicher, dass das Zielverzeichnis existiert..."
rclone mkdir "$backup_target$current_date" || log "Zielverzeichnis konnte nicht erstellt werden"

# === Alte Backups löschen ===
log "Prüfe vorhandene Backup-Monate..."
months=$(rclone lsf --dirs-only "$backup_target" | sort)
months_array=($months)
months_count=${#months_array[@]}

if [ "$months_count" -gt "$retention_limit" ]; then
  months_to_delete_count=$((months_count - retention_limit))
  log "Lösche $months_to_delete_count alte Monat(e)..."
  
  for i in $(seq 0 $((months_to_delete_count - 1))); do
    month_to_delete="${months_array[$i]}"
    log "Lösche Monat: $month_to_delete"
    rclone purge "$backup_target$month_to_delete" >> "$logfile" 2>&1 || log "Fehler beim Löschen von $month_to_delete"
  done
else
  log "Keine alten Backups zu löschen."
fi

# === Upload-Dateien vorbereiten (alle .zst mit aktuellem Monat) ===
log "Suche nach Dateien mit *$month_tag*.zst..."
find "$backup_source" -type f -name "*$month_tag*.zst" -printf "%P\n" > "$upload_list"

if [ ! -s "$upload_list" ]; then
  log "⚠️  Keine .zst-Dateien des Monats $month_tag gefunden – Upload übersprungen."
  rm -f "$upload_list"
  exit 0
fi

# === Screen Session starten ===
if screen -list | grep -q "$screen_name"; then
  log "Screen-Session '$screen_name' existiert bereits. Beende vorherige Session..."
  screen -S "$screen_name" -X quit
fi

log "Starte Upload in Screen-Session '$screen_name'..."
screen -dmS "$screen_name" bash -c "
  rclone copy \"$backup_source\" \"$backup_target$current_date\" \
    --files-from \"$upload_list\" \
    --progress \
    --log-file \"$temp_log\" \
    --log-level INFO
"

# Upload läuft
log "Upload läuft – Fortschritt über 'screen -r $screen_name' abrufbar."

# Warten auf Upload-Ende
while screen -list | grep -q "$screen_name"; do
  sleep 10
done

# === Upload-Zusammenfassung ===
transferred=$(grep "Transferred:" "$temp_log" | tail -n 1 | awk '{print $2 " " $3}')
speed=$(grep "Average speed:" "$temp_log" | tail -n 1 | awk '{print $3 " " $4}')
elapsed=$(grep "Elapsed time:" "$temp_log" | tail -n 1 | awk '{print $3 " " $4}')
errors=$(grep "ERROR :" "$temp_log" | tail -n 10 || true)

log "Upload abgeschlossen – Größe: $transferred, Zeit: $elapsed, Geschwindigkeit: $speed"

# === Text-Mail erstellen ===
subject="Rclone Backup – $(date '+%Y-%m-%d %H:%M')"
if [ -z "$errors" ]; then
  status="✅ Upload erfolgreich"
else
  status="❌ Fehler beim Upload"
fi

mail_body=$(cat <> "$logfile"
rm -f "$temp_log" "$upload_list"

log "===== Backup-Vorgang beendet ====="

🌐 Script 2: rclone_backup_dualwan.sh

This optional script routes all upload traffic via a secondary WAN (not fallback, but for dedicated uplink). Adjust the gateway IP and device as needed.

Make sure the routing table 200 secondgw exists in /etc/iproute2/rt_tables:

echo "200 secondgw" >> /etc/iproute2/rt_tables
#!/bin/bash
set -euo pipefail

# === Root-Check ===
if [[ $EUID -ne 0 ]]; then
  echo "Dieses Skript muss als root ausgeführt werden."
  exit 1
fi

# === Einstellungen ===
SECOND_GW="192.168.123.2"
TABLE_NAME="secondgw"
TABLE_ID="200"
RCLONE_BACKUP_SCRIPT="/usr/local/bin/rclone_backup.sh"
DEVICE="vmbr0"

# === Logging ===
logfile="/var/log/rclone_backup_dualwan.log"
log() {
  echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$logfile"
}

log "===== Starte Dual-WAN Backup über $SECOND_GW ====="

# === Routing-Tabelle sicherstellen ===
if ! grep -q "$TABLE_ID $TABLE_NAME" /etc/iproute2/rt_tables; then
  echo "$TABLE_ID $TABLE_NAME" >> /etc/iproute2/rt_tables
  log "Routing-Tabelle '$TABLE_NAME' mit ID $TABLE_ID eingetragen."
fi

# === Routen setzen ===
ip route flush table "$TABLE_NAME" || true
ip route add default via "$SECOND_GW" dev "$DEVICE" table "$TABLE_NAME"
ip rule add fwmark 0x1 table "$TABLE_NAME"
iptables -t mangle -A OUTPUT -p tcp --dport 443 -m owner --uid-owner root -j MARK --set-mark 1

# === Backup starten ===
log "Starte Backup über sekundäres Gateway ..."
if bash "$RCLONE_BACKUP_SCRIPT"; then
  log "✅ Backup erfolgreich abgeschlossen."
else
  log "❌ Fehler beim Backup!"
fi

# === Cleanup ===
log "Entferne temporäre Routing-Regeln ..."
iptables -t mangle -D OUTPUT -p tcp --dport 443 -m owner --uid-owner root -j MARK --set-mark 1 || true
ip rule del fwmark 0x1 table "$TABLE_NAME" || true
ip route flush table "$TABLE_NAME" || true

log "Dual-WAN-Backup beendet."

🔐 Rclone Encrypted Remote

Define your encrypted remote in ~/.config/rclone/rclone.conf like this:

[idrive]
type = s3
provider = Other
access_key_id = YOUR_IDRIVE_KEY
secret_access_key = YOUR_IDRIVE_SECRET
endpoint = https://xxxxx.idrivee2-22.com

[idrive-enc]
type = crypt
remote = idrive:/proxmox-backups
filename_encryption = off
directory_name_encryption = false
password = YOUR_ENCRYPTED_PASSWORD
password2 = YOUR_ENCRYPTED_SALT

♻️ Restore from iDrive E2

To restore a backup:

  1. rclone ls idrive-enc:2025-06
  2. rclone copy idrive-enc:2025-06/vzdump-qemu-100-2025_06_01.vma.zst /var/lib/vz/dump/
  3. qmrestore /var/lib/vz/dump/vzdump-qemu-100-2025_06_01.vma.zst 100

✅ Optional Integrity Check

zstd -t /var/lib/vz/dump/vzdump-qemu-100-2025_06_01.vma.zst

📅 Cronjob Example

0 3 * * * /usr/bin/screen -dmS rclone-backup /root/rclone_backup.sh

✅ Final Notes

  • 🌐 Routes only upload traffic via secondary WAN
  • 🔐 Fully encrypted contents via rclone crypt
  • 🧹 Auto-cleanup of old backups
  • 📧 Email notification after every run