Automated backup transfer script for Proxmox VE that moves backup files from the local dump directory to a mounted SMB share with live progress tracking.
This project provides a robust backup transfer solution that:
- Moves Proxmox backup files (
.tar.zstfor containers,.vma.zstfor VMs) from the source directory to an SMB share - Organizes backups into date-based subdirectories (e.g.,
2026-06-09/) - Provides a live web dashboard showing transfer progress
- Uses tmux for SSH resilience
- Verifies file integrity with SHA-256 checksums
- Includes a dry-run mode for safe testing
- tmux Resilience: Automatically wraps execution in a tmux session. If SSH disconnects, reattach with
tmux attach -t backup-transfer - Live Dashboard: Real-time web interface showing progress, statistics, and file-by-file status
- Date-Based Organization: Extracts dates from filenames and creates subdirectories (e.g.,
proxmox-backups/2026-06-09/) - SHA-256 Verification: Verifies checksums before and after transfer, only deletes source files after successful verification
- Checksum Storage: Creates
.sha256files alongside each backup and generates session manifests - Verify Mode: Run
--verifyto check all stored backups against their checksums - Sidecar File Handling: Moves associated
.logand.notesfiles alongside backups - Dry-Run Mode: Safe testing with fake files in temporary directories
- Smart Skipping: Skips files that already exist at destination with matching checksums
- Proxmox VE server
- Python 3 (for HTTP dashboard server)
- tmux (for session management)
- Caddy (optional, for reverse proxy access)
Copy .env.example to .env in the script directory and configure:
cp .env.example .env
nano .envRequired:
SMB_SHARE— name of the SMB share (mounted at/mnt/<SMB_SHARE>)
Optional (defaults shown):
SRC_DIR—/var/lib/vz/dump— Proxmox backup source directoryBACKUP_SUBDIR—proxmox-backups— subdirectory under SMB mountWEB_PORT—8080— dashboard HTTP server portWEB_DIR—/var/www/backup-status— dashboard files locationLOG_DIR—/var/log— log file directorySCRIPT_DIR—~/move-backups— where script and index.html are deployedDASHBOARD_TIMEOUT—60— seconds to keep dashboard up after completionDRY_RUN_TIMEOUT—30— seconds for dry-run dashboard availability
Deploy the script and dashboard to your Proxmox server:
scp move-backups.sh index.html root@<PROXMOX_IP>:~/move-backups/
ssh root@<PROXMOX_IP> "chmod +x ~/move-backups/move-backups.sh"~/move-backups/move-backups.shThe script will:
- Create a tmux session named
backup-transfer - Start a web dashboard on port 8080
- Transfer all
.tar.zstand.vma.zstfiles from the source directory to the SMB share destination - Organize files into date-based subdirectories
- Verify checksums and delete source files only after successful verification
- Keep the dashboard available for 5 minutes after completion
~/move-backups/move-backups.sh --dry-runCreates 10 fake backup files in /tmp/backup-test-src/ and runs the full transfer logic without touching real data. Useful for:
- Testing the script works correctly
- Verifying dashboard displays progress
- Confirming date-based folder organization
- Testing sidecar file handling
Auto-cleans temporary directories if all checksums pass. Dashboard available for 30 seconds after completion.
~/move-backups/move-backups.sh --verifyVerifies all backup files in the SMB share against their stored SHA-256 checksums. This mode:
- Scans the destination directory for all
.sha256files - Runs
sha256sum -con each to verify integrity - Reports: X passed, Y failed, Z missing
- Exits with code 1 if any failures detected
Useful for periodic integrity checks of your backup archive.
The script creates two types of checksum records:
Per-file checksums (.sha256 files):
- Created alongside each backup file (e.g.,
backup.tar.zst.sha256) - Format:
<hash> <filename>(standard sha256sum format) - Can be verified individually:
sha256sum -c backup.tar.zst.sha256
Session manifest files:
- Created after each transfer run in the destination root
- Format:
checksums-YYYYMMDD-HHMMSS.txt - Contains all hashes from that transfer session
- Provides complete audit trail
Direct access:
http://<PROXMOX_IP>:8080
Via Caddy reverse proxy:
https://backups.<YOUR_DOMAIN>
The dashboard shows:
- Progress bar with percentage
- Statistics: transferred, skipped, failed counts
- Current file being processed
- Elapsed time
- List of completed files with status (transferred/skipped/failed)
- Auto-refreshes every 2 seconds
- Displays "Transfer Complete" banner when finished
If SSH disconnects during transfer:
# Reattach to the running session
tmux attach -t backup-transferThe tmux session persists until the script completes and the cleanup trap fires.
Add this route to your Caddyfile (typically on your reverse proxy server):
backups.<YOUR_DOMAIN> {
reverse_proxy http://<PROXMOX_IP>:8080
}Reload Caddy after adding the route:
sudo systemctl reload caddyproxmox-backup/
├── move-backups.sh # Main transfer script with tmux, dashboard, and dry-run support
├── index.html # Dashboard UI (copied to WEB_DIR at runtime)
├── Caddyfile # Complete Caddy config with backup dashboard route
└── .gitattributes # Enforces LF line endings
Transfer logs are written to:
Normal mode:
$LOG_DIR/backup-transfer-YYYYMMDD-HHMMSS.log
Dry-run mode:
/tmp/backup-transfer-test.log
Logs include timestamps, file processing status, checksums, and any errors.
- Initialization: Creates tmux session, sets up directories, starts Python HTTP server
- File Discovery: Scans source directory for
.tar.zstand.vma.zstfiles - Date Extraction: Parses dates from filenames (e.g.,
2026_06_09→2026-06-09) - Transfer Loop: For each file:
- Computes SHA-256 checksum of source file
- Checks if destination exists with matching checksum (skip if identical)
- Copies file to date-based subdirectory
- Verifies destination checksum matches source
- Creates
.sha256checksum file alongside destination backup - Moves sidecar files (
.log,.notes) - Deletes source file only after verification
- Updates dashboard status.json
- Manifest Generation: Creates
checksums-YYYYMMDD-HHMMSS.txtwith all transfer hashes - Cleanup: Stops HTTP server after 5 minutes (30 seconds for dry-run), removes temp dirs in dry-run mode
Dashboard not accessible:
- Check if script is running:
ps aux | grep move-backups - Check if port 8080 is listening:
netstat -tlnp | grep 8080 - Check logs in
$LOG_DIR/backup-transfer-*.log
tmux session issues:
- List sessions:
tmux ls - Kill stuck session:
tmux kill-session -t backup-transfer - Reattach:
tmux attach -t backup-transfer
Dry-run cleanup failed:
- Manual cleanup:
rm -rf /tmp/backup-test-src /tmp/backup-test-dst /tmp/backup-test-web