Hobby projects → Our setup → Backups
Deeper dive · Backups · September 2026

What backs up what

Every virtual machine in the house is captured nightly, every container daily without stopping, and both copies leave the building: the machine history to Cloudflare R2, pulled by a box that can only read; the container archives and the photo library to Dropbox, behind six checks that stop a bad day from being mirrored. Nothing counts as backed up until it has been read back and matched. This page is the whole chain, with the numbers.

The chain in numbers

Nightly
every virtual machine
both hypervisors, deduplicated 10–16×
0
containers stopped
the daily archive comes from a snapshot
868 GiB
offsite in R2
every chunk read back from R2 and matched — 100 %
28 vs 5
snapshots per VM
kept offsite vs kept locally
~158 GiB
to Dropbox, daily
container archives, flash images, photos
6
guards on the mirror
before it may delete anything at the far end

The shape of it

Three layers, in the order you would restore from them. The first two are point-in-time copies with retention; the third is verification, because a backup that has never been read back is a hope.

WhatPoint in time, locallyOffsiteChecked by
Virtual machines
the smart-home brain, the service host, the GPU server, the game servers, the security monitor
Proxmox Backup Server on the storage server, nightly, deduplicated; last five kept, and the hypervisor’s own configuration on a 14-daily / 8-weekly / 6-monthly ladderCloudflare R2, pulled each morning by a read-only token; 14 daily, 8 weekly, 6 monthlyWeekly verification that reads the chunks back from R2 and re-hashes them
The appliance’s guests
the voice stack, the demo house, the AI NAS
Their own datastore on the same backup server, nightlyThe same R2 bucket, in its own namespace so nothing collidesThe same weekly verify
Containers
36 on the storage server: media, downloads, monitoring
A Btrfs snapshot of appdata at 08:30 while everything keeps running; one compressed archive per container; five days kept, with the flash drive and the container templates beside themDropbox at 11:00, mirroring the dated runsA manifest per run, a notification on any failure, and archives opened from a scratch folder to prove they extract
The photo libraryThe array, behind dual parityDropbox, in the same daily mirrorThe mirror’s own six guards
The backup server itselfIts system disk imaged daily at 06:00 by the Unraid VM Backup plugin, two kept, into the same backups shareDropbox, with the rest of that share— the data it protects is already in R2; the image only saves reinstalling it

Everything on the household hypervisor, including Home Assistant, is a virtual machine, so the smart-home brain restores as a whole machine from any of its nightly snapshots.

Virtual machines — Proxmox Backup Server

Both hypervisors back their guests up to a Proxmox Backup Server that runs as a VM on the storage server, with its datastores on the array. Deduplication is what makes nightly full snapshots affordable: the household host’s store holds 671 GiB on disk for a dedup factor of 9.6, the appliance’s 197 GiB at 16×. The retention rules are simple locally — the last five snapshots per machine — because the long history lives offsite, where it is safer.

Two housekeeping facts worth knowing if you run one of these. A prune job with no depth limit recurses into every namespace beneath it, so a root-level “keep last five” will quietly flatten a longer ladder kept in a sub-namespace; each prune job here is pinned to its own depth. And a machine that stops sending backups keeps its last N snapshots forever, because prune only ever counts what arrives: retired guests are forgotten by hand, and the space comes back on the second garbage collection, after the 24-hour grace window.

Containers — a snapshot, not a stop

The 36 containers on the storage server are backed up from a Btrfs snapshot of the appdata pool: every container is paused for the instant the snapshot commits — the same freeze Proxmox applies to a container in its own snapshot mode — then unpaused, and the archives are written from the frozen tree while the live one carries on. One .tar.zst per container, about 130 GiB a day, five days kept, restorable from the web GUI. The whole method, with its scripts and the exact plugin settings, is the appdata snapshots guide.

Offsite one — the machine history goes to Cloudflare R2

Pull, never push

A second backup server on the appliance does the moving

The backup server that holds the data holds no cloud credentials at all. A second Proxmox Backup Server, a small VM on the appliance with a 128 GB local cache disk, holds a read-only token for the first one and pulls each morning at 07:30 into an S3-backed datastore in R2. Whoever compromised the storage server would find nothing there that reaches the offsite copy. Deletions never propagate either: the sync jobs are set never to remove what has vanished at the source, so offsite retention is owned entirely by its own prune job — 14 daily, 8 weekly, 6 monthly, 28 snapshots per machine against 5 at home. That gap is deliberate: it is the dwell time a quiet intruder would need to outwait.

  • Bucket953 GB after both seeds671 GiB household host + 197 GiB appliance; namespaces keep the two sets of VM numbers apart
  • Seed3h47m for the appliance’s 197 GiBbyte count matched the source exactly; about 15 MiB/s per worker, one machine at a time
  • Daily1½ to 10 minutesonly new chunks travel
  • Egresszeroreading the whole copy back costs nothing, which is what makes weekly verification possible
  • VerifySaturdays, on a clean baselinethe baseline pass read all 868 GiB back from R2 with nothing skipped and every chunk matched its hash, so “verified” means verified here, not before upload
  • Day07:30 sync · 09:00 prune · 10:00 garbage-collectin that order, never overlapping

The provider was measured, not assumed. Round-trip times from Bangkok to the candidates: a Singapore object store 55 ms, two European ones around 190 ms, US regions 240–270 ms. R2 sits behind anycast, so its round trip says nothing; a real 2 GiB transfer measured 24 MiB/s up at default settings and 57 MiB/s with upload concurrency raised to 32, 50 MiB/s down. The Singapore option lost despite the latency: its 90-day minimum retention fights a backup server’s constant chunk churn, and its egress is capped against stored volume — which penalises verification, the one operation that matters most. R2 was chosen for zero egress, which makes reading every byte back as often as you like free; on a provider that charges for it, a documented backup-server case saw verification traffic alone multiply the monthly bill several times over.

What the first full verification taught. The first attempt at reading 868 GiB back over one connection surfaced a handful of dropped reads on the four largest disks — no hash mismatches anywhere, just transient connection errors that a re-read clears. The reader count was set from eight to four, and the baseline pass that followed came back 100 % clean: every chunk read from R2, every hash matched. From here the weekly job only re-reads what is new, with anything older than 30 days re-checked on its own cycle.

Offsite two — container archives and photos go to Dropbox

A mirror that checks before it deletes

Daily at 11:00, once the 08:30 container run has finished writing

The backups share — the dated container runs, the backup server’s own disk image, the flash-drive images and the book catalogue’s database snapshots — and the photo library are mirrored to Dropbox by rclone from the GPU server, which runs the household’s scheduled jobs. The share is the source of truth and Dropbox follows it, deletions included; what makes that safe is that the share holds five point-in-time runs, not a live folder. A mirror mirrors deletions, so the dated, immutable archives are the layer you restore from and the mirror is the copy of them that lives elsewhere.

  • Six guardsbefore it will delete anythingshare mounted; runs exist; newest run under 48 hours old; newest run holds container archives; nothing written into it for 15 minutes (waits up to 90); and the count of complete runs at the destination not collapsing
  • Delete afternot duringthe expired run is removed only once the new one has fully landed, so a failed upload can never leave fewer good copies than before
  • Newest firsteight parallel transferstoday’s backup lands before any backlog; the daily 158 GiB takes about two hours
  • Throughput33–38 MiB/s aggregate2–8 MiB/s per stream: the account limit is the total, so parallelism is the lever; the one catch-up run moved 504 GiB in 4h25m
  • Excludedthe book pipeline’s working foldersprocessed and quarantined files churn all day; the catalogue databases are what matter

A day in the life

TimeJobWhere
overnightEvery guest on both hypervisors to the backup server; the hypervisor’s own configuration at 03:00 on its long ladder; local prune at midnightStorage server
06:00The backup server’s system disk imaged, two keptStorage server
07:30Both datastores pulled to R2Appliance → R2
08:30Containers paused, snapshotted, unpaused; one archive per container written until about 10:25Storage server
09:00 / 10:00Offsite prune, then offsite garbage collectionR2
11:00Backups share and photo library mirrored to Dropbox, about two hoursGPU server → Dropbox
Saturday 11:00Verification reads the week’s new chunks back from R2 and re-hashes themR2

The order is the point: nothing reads a folder another job is still writing, and the offsite prune runs after the sync has delivered the day’s snapshots, never before.

The rules underneath

The drives themselves are watched too: per-disk temperatures feed Home Assistant and the house cools its own drives when the hot season pushes them; services on network storage wait for the NAS behind a mount guard rather than starting on empty folders. Both are on the servers page.