# PBS Sync Pipeline: Local → Remote with Verify + Prune ## When to Use When you need a multi-stage backup pipeline: backup locally for fast restores, async-push to offsite PBS, verify on both sides, then prune local aggressively while retaining remote long-term. This is the "tiered backup" pattern. ## ⚠️ Important: Direct vzdump May Be Better Than Sync **Confirmed 2026-07-05**: The sync pipeline sounds elegant but the initial push sync over WAN is **impractically slow**. In practice: - A 650 GB initial sync at measured 2.7 KB/s actual throughput (not the theoretical 5-7 MB/s) would take **weeks**, not hours. - PBS push sync has significant protocol overhead — many small chunk round-trips over WAN make it far slower than raw vzdump. - Meanwhile, **direct vzdump to the remote PBS** already populated the remote datastore with the same deduplicated chunks. - When the sync job finally ran, it reported `"found no new data to push"` because the remote already had identical chunks from direct vzdump. ### Recommended Strategy | Pattern | When to Use | Why | |---------|------------|-----| | **Direct vzdump → remote PBS** | Always, for ongoing offsite backups | Dedup handles incrementals naturally, no sync overhead | | **Local PBS (separate job)** | For fast local restores | Independent schedule + retention | | **Push sync job** | Only if local PBS has backups the remote DOESN'T have | E.g. migrating to a new remote PBS | Don't set up the sync pipeline expecting it to be the primary offsite mechanism. Set up **two independent vzdump jobs** (local + remote) with different retention. The sync job is a migration tool, not a daily driver. ### ⚠️ Sync "found no new data to push" — Check for Owner Mismatch If `sync-job run` immediately returns `"found no new data to push"` but you know the remote is missing backups that exist locally, **the local backups may have been failing silently for months due to an owner mismatch** (see `references/pbs-lxc-setup-2026-07.md` pitfall #11). Debugging path (discovered 2026-07-05): 1. Check PVE task history for the local backup job — look for non-OK statuses: ```bash pvesh get /nodes//tasks --limit 50 --output-format json | python3 -c " import json,sys for t in json.load(sys.stdin): if t.get('type')=='vzdump': print(f\"{t['starttime']} {t['status']} id={t.get('id','')}\")" ``` Status `job errors` (not `OK`) means individual VM backups failed. 2. Read the task log to find the owner error: ```bash pvesh get /nodes//tasks//log --output-format json ``` Look for: `backup owner check failed (admin@pbs != root@pam)` 3. Check owner files on the PBS datastore: ```bash pct exec 116 -- bash -c " for d in /mnt/datastore/noris/vm/*/ /mnt/datastore/noris/ct/*/; do echo \"$(echo $d | sed 's|.*/noris/||;s|/$||'): $(cat ${d}owner)\" done" ``` 4. Fix: overwrite all owner files to match the PVE storage config username: ```bash pct exec 116 -- bash -c ' for d in /mnt/datastore/noris/vm/*/ /mnt/datastore/noris/ct/*/; do echo "admin@pbs" > "${d}owner" done' ``` 5. After fixing, trigger a manual backup to confirm it works: ```bash # On the PVE node where the VM runs vzdump 106 --storage noris_s3 --mode snapshot --quiet 1 ``` Watch the PBS task log for progress (not the PVE log — PVE `--quiet` suppresses progress output): ```bash pct exec 116 -- find /var/log/proxmox-backup/tasks -name "**" -exec tail -5 {} \; ``` **Root cause explained**: The local backup job was failing every night with `backup owner check failed`, but the overall job status showed `job errors` (not a hard crash), and other VMs in the same job succeeded. Meanwhile, the remote PBS already had backups from the direct `offsite-ha-daily` vzdump job at 02:00. So when the sync job ran, the remote already had the same deduplicated chunks — hence "no new data to push". The sync wasn't broken; the **local backups were never being written**. ### Measuring Actual WAN Throughput Don't trust the PBS task log — it goes silent during transfer. Measure actual network throughput with `/proc/net/dev` deltas: ```bash # On local PBS (CT 116): T1=$(ssh root@pve-node "pct exec 116 -- cat /proc/net/dev | grep eth0" | awk '{print $2}') sleep 5 T2=$(ssh root@pve-node "pct exec 116 -- cat /proc/net/dev | grep eth0" | awk '{print $2}') echo "RX rate: $(( (T2 - T1) / 5 )) bytes/sec" # On remote PBS: T1=$(ssh ubuntu@ 'cat /proc/net/dev | grep ens3' | awk '{print $2}') sleep 5 T2=$(ssh ubuntu@ 'cat /proc/net/dev | grep ens3' | awk '{print $2}') echo "RX rate: $(( (T2 - T1) / 5 )) bytes/sec" ``` If RX rate is < 5 KB/s while a sync task shows "running", the sync is effectively stalled — the PBS sync protocol is doing negligible work. Kill it and use direct vzdump instead. ## Architecture ``` 23:00 vzdump → lokaler PBS (noris datastore) ↓ 02:30 verify-noris (local integrity check) ↓ 03:00 sync-to-offsite (push last backup → remote PBS) 03:00 prune-offsite (remote: keep-last=3, weekly=4, monthly=6) ↓ 04:00 verify-offsite (remote integrity check) ↓ 05:00 prune-noris (local: keep-last=1 only — frees space) ``` ### Key Design Decisions 1. **Push direction**: Local PBS pushes to remote (not pull). Remote PBS doesn't initiate connections to the local network. 2. **`--transfer-last 1`**: Only sync the most recent snapshot per group. Reduces bandwidth on daily sync. 3. **Local keep-last=1**: After sync+verify, only the latest backup stays local for quick restore. Older backups are pruned. 4. **Remote long retention**: keep-last=3 + keep-weekly=4 + keep-monthly=6 provides ~6 months of offsite history. 5. **PBS has no native "delete-local-after-remote-verify"**: The pipeline approximates this with staggered schedules. Between 03:00 (sync) and 05:00 (local prune), the backup exists on both sides. ## Prerequisites - Local PBS (e.g. CT 116) with datastore `noris` - Remote PBS (e.g. OpenStack VM) with datastore `offsite` - Remote PBS registered on local PBS as a "remote" ## Step-by-Step Setup ### 1. Register Remote PBS on Local PBS ```bash # On local PBS (inside CT 116 via pct exec) proxmox-backup-manager remote create noris-offsite \ --host \ --port 8007 \ --auth-id admin@pbs \ --password # CRITICAL: Add fingerprint — push will fail with SSL cert error without it # Get fingerprint from remote PBS: # ssh ubuntu@ 'sudo proxmox-backup-manager cert info | grep Fingerprint' proxmox-backup-manager remote update noris-offsite \ --fingerprint # Verify proxmox-backup-manager remote list ``` ### 2. Create Push Sync Job ```bash proxmox-backup-manager sync-job create sync-to-offsite \ --remote noris-offsite \ --remote-store offsite \ --store noris \ --sync-direction push \ --schedule 03:00 \ --transfer-last 1 ``` ### 3. Create Local Verify Job ```bash proxmox-backup-manager verify-job create verify-noris \ --store noris \ --schedule 02:30 ``` ### 4. Create Local Prune Job (Aggressive) ```bash proxmox-backup-manager prune-job create prune-noris \ --store noris \ --schedule 05:00 \ --keep-last 1 \ --comment "Minimal local retention after offsite sync" ``` ### 5. Create Remote Verify + Prune Jobs On the remote PBS: ```bash # Verify job proxmox-backup-manager verify-job create verify-offsite \ --store offsite \ --schedule 04:00 # Prune job (long retention) proxmox-backup-manager prune-job create prune-offsite \ --store offsite \ --schedule 03:00 \ --keep-last 3 \ --keep-weekly 4 \ --keep-monthly 6 ``` ### 6. Run Initial Sync Manually The first sync transfers all data (full, not incremental). Run manually to avoid timing out the scheduled job: ```bash # On local PBS proxmox-backup-manager push noris noris-offsite offsite --transfer-last 1 ``` This may take a long time (hours for hundreds of GB). Subsequent syncs are incremental (only new chunks transferred). ### 7. Verify All Jobs ```bash # Local PBS proxmox-backup-manager verify-job list proxmox-backup-manager prune-job list proxmox-backup-manager sync-job list # ⚠️ see pitfall #1 # Remote PBS proxmox-backup-manager verify-job list proxmox-backup-manager prune-job list ``` ## Manual Verification Run a one-time verify on any PBS datastore: ```bash proxmox-backup-manager verify ``` Example output (successful): ``` verify datastore offsite found 2 groups verify group offsite:ct/116 (1 snapshots) verified 382.77/967.64 MiB in 1.89 seconds (0 errors) verify group offsite:vm/106 (2 snapshots) verified 17222.64/52576.00 MiB in 103.62 seconds (0 errors) TASK OK ``` ## Monitoring Running Tasks ```bash # List running/completed tasks proxmox-backup-manager task list # View task log proxmox-backup-manager task log # Stop a running task proxmox-backup-manager task stop ``` ## Sync-Job Config File Location Sync job configs are stored at `/etc/proxmox-backup/sync.cfg`: ``` sync: sync-to-offsite comment Push last backup to offsite remote noris-offsite remote-store offsite schedule 03:00 store noris sync-direction push transfer-last 1 ``` ## Pitfalls 1. **`sync-job list` may not display push jobs** — In PBS 3.4.8, `sync-job list` returns empty even when a push sync job exists. The job IS created and stored in `/etc/proxmox-backup/sync.cfg`. Verify by reading the config file directly. The scheduled job still runs correctly. 2. **Remote fingerprint required before push** — `proxmox-backup-manager push` fails with `SSL routines:tls_post_process_server_certificate:certificate verify failed` if the remote's fingerprint isn't registered. Always run `remote update --fingerprint` after `remote create`. 3. **`prune-job create` rejects keep-*=0** — PBS enforces minimum value of 1 for all keep-* parameters. To effectively disable a retention dimension, simply omit it (don't set it to 0). E.g. `--keep-last 1` alone keeps only 1 backup with no weekly/monthly/yearly retention. 4. **Initial sync is VERY slow — plan for hours, not minutes** — First push transfers all deduplicated chunks. For ~650 GB at ~5-7 MB/s WAN speed, expect **25-30+ hours** for the initial sync. The 300s timeout is nowhere near enough. Run manually with a long timeout or let the scheduled job handle it overnight (or over multiple nights). Subsequent daily syncs are incremental and transfer only new/changed chunks (typically MB, not GB). **User reality check**: "in einer stunde dürfte das nicht hochgeladen sein" — correct. At 5 MB/s, 650 GB takes ~36 hours. Always calculate: `hours = total_GB * 1024 / (speed_MBps * 3600)`. 5. **Push sync task log shows NO progress** — The task log only records the initial lines ("Found 9 groups to sync", "skipped: N snapshot(s)") and then goes silent for the entire transfer. No progress bars, no chunk counts. To monitor actual progress, check the **remote side**: ```bash # On remote PBS — watch disk usage grow ssh ubuntu@ 'df -h /mnt/datastore/offsite; sudo du -sh /mnt/datastore/offsite/.chunks/' ``` Compare before/after to estimate transfer rate. The `.chunks/` directory grows as chunks arrive. 6. **`--transfer-last 1` with many groups still moves lots of data** — `--transfer-last 1` syncs 1 snapshot PER GROUP. With 9 groups (multiple VMs/CTs), that's 9 snapshots total. Large VMs (e.g. 50+ GB HA VM) dominate the transfer. Consider filtering groups with `--group-filter` to sync only critical VMs first, then expand. 7. **GC (garbage collection) runs by default** — PBS enables daily GC at midnight by default. No explicit configuration needed. GC reclaims space from pruned chunks. 6. **Schedule staggering matters** — Ensure verify runs BEFORE sync (so you don't push corrupt data) and local prune runs AFTER sync+remote-verify (so you don't delete local before remote is confirmed good). 7. **`--transfer-last N` limits per-group** — `--transfer-last 1` syncs only the most recent snapshot PER backup group (per VM/CT). If you have 9 groups, it syncs 9 snapshots (one per group), not 1 total. ## Related References - `references/openstack-offsite-pbs-2026-07.md` — Remote PBS provisioning on OpenStack, PVE integration, HA backup options - `references/pbs-lxc-setup-2026-07.md` — Local PBS in LXC setup, RBD/Ceph workarounds, PVE integration, **owner-mismatch pitfall (#11)** and **skip-external-VMs pitfall (#12)** - `references/infra-monitoring-2026-07.md` — Prometheus + Grafana monitoring stack for PVE/Ceph/PBS/Galera - `templates/openstack-pbs-setup.sh` — Reusable PBS install script for OpenStack VMs