Storage Backup Strategies - 3-2-1, RPO/RTO, and Backup Tiering
Status: Active
Last Updated: 2026-08-26
Category: Cloud - Storage & Backup Fundamentals
Prerequisites: cloud-storage-concepts, restic-backups
Time: 2 hours (reading + planning)
Tags: backup, 3-2-1, rpo, rto, storage, disaster-recovery
Summary
This article defines the strategy layer underneath every backup job on fogserv.cloud: the 3-2-1 rule, recovery point/recovery time objectives (RPO/RTO), and the three fundamental backup models (snapshot, sync, versioned). It then maps those concepts onto a practical tiering scheme β local disk β MinIO object storage β offsite copy β so that each service's backup can be designed deliberately instead of ad hoc.
π― What You'll Learn
By the end of this article, you'll be able to:
- β Classify any data set by its RPO/RTO requirements
- β Choose between snapshot, sync, and versioned backup per workload
- β Design a tiered local β object β offsite pipeline
- β Avoid the classic failure modes that make backups unrestorable
Table of Contents
- Context / Why This Matters
- The 3-2-1 Rule
- RPO and RTO
- Snapshot vs Sync vs Versioned
- Tiering: Local β Object β Offsite
- Practical Examples
- Troubleshooting & Common Pitfalls
Context / Why This Matters
fogserv.cloud already runs the mechanics of backups: restic-backups covers the tool, minio-setup covers S3-compatible object storage, and nextcloud-setup covers the largest user-facing data set. What those articles deliberately do not answer is the strategic question: how much data are you allowed to lose, and how fast must you be back?
Without explicit RPO/RTO numbers, backup systems drift into a false comfort state: jobs run green every night, but a single house fire, ransomware event, or dead RAID controller still takes everything, because all copies shared a failure domain. Strategy first, tooling second.
The 3-2-1 Rule
The baseline for any data you would be sad to lose:
| # | Requirement | fogserv.cloud implementation |
|---|---|---|
| 3 | Three copies of the data | Primary (service volume) + local backup target + offsite copy |
| 2 | Two different media/types | Local filesystem (ZFS/ext4 on the NAS) + S3 object store (MinIO) |
| 1 | One copy offsite | Encrypted restic repo pushed to a remote location (friend's box, cheap VPS, or cloud S3) |
Notes on applying it honestly:
- "Different media" means different failure modes. A second disk in the same chassis is not a second medium β it shares the power supply, the controller, and the fire.
- Offsite does not have to mean "cloud vendor." An encrypted restic repository synced to a family member's server over WireGuard satisfies rule #1 as long as it is physically separate.
- The rule extends to 3-2-1-1-0 in stricter environments: one copy offline/immutable (air-gapped or object-lock enabled), zero verification errors. Restic's
checkcommand plus MinIO bucket versioning get you most of the way there.
RPO and RTO
Two numbers define every backup decision. Write them down per service before choosing tooling.
RPO β Recovery Point Objective
How much data loss is tolerable, measured in time.
- Nextcloud document edits: users expect minutes of loss at worst β RPO of 15β60 min via database WAL/binlog + frequent file sync
- Immich photo library: new photos arrive daily β RPO of 24 h is acceptable
- Config directories (
/etc, compose files): change rarely β RPO of days is fine
RTO β Recovery Time Objective
How long until the service is usable again after an incident.
- "Nextcloud back up by end of day" β RTO β 8 h, allows bare-metal-style full restore
- "Just this one folder, right now" β RTO β minutes, requires per-file browsing of backups (this favors restic/Borg-style content-addressable stores over opaque tarballs)
Mapping table for homelab services
| Service | RPO | RTO | Consequence |
|---|---|---|---|
| Nextcloud DB | 15 min | 2 h | Frequent small pg_dump snapshots + nightly full |
| Nextcloud data dir | 24 h | 4 h | Nightly restic snapshot |
| Immich (DB + uploads) | 24 h | 6 h | Nightly combined dump+snapshot |
| MinIO buckets | 24 h | 8 h | mc mirror + lifecycle-managed versions |
| Docker host config | 7 days | 30 min | Git repo (see ../cicd/cicd-concepts.md) |
Snapshot vs Sync vs Versioned
Three fundamentally different models. Most bad backup designs come from using one where another was needed.
Snapshot
A point-in-time, read-only view of the data (ZFS/btrfs snapshots, LVM snapshots, database dumps).
- Pros: Near-instant creation, cheap, great RPO granularity
- Cons: Usually lives on the same storage pool β counts toward copy #1 only, not 3-2-1 compliance
- Use when: You need crash consistency (e.g., snapshot the Postgres volume before restic reads it)
Sync (mirroring)
A live copy kept identical to the source (rsync, mc mirror). Deleting a file on the source deletes it in the mirror.
- Pros: Simple, fast restore, low storage cost
- Cons: Not a backup against deletion, ransomware, or corruption β the damage replicates immediately
- Use when: Fast bulk transfer between tiers, never as the only protection layer
Versioned backup
Each run stores a new immutable generation; old generations expire on a retention policy (restic snapshots, Borg archives).
- Pros: Survives source-side deletion/ransomware; you choose how far back to go
- Cons: Storage grows with retention; restores need the tool
- Use when: This should be your default model for anything irreplaceable
Rule of thumb: snapshot for consistency points, sync for moving data between tiers, versioned backup as the actual safety net.
Tiering: Local β Object β Offsite
A practical three-tier pipeline used across fogserv.cloud services:
βββββββββββββββ snapshot ββββββββββββββββββββ restic push βββββββββββββββββββ
β Service β ββββββββββββΆ β Tier 1: Local β ββββββββββββΆ β Tier 2: MinIO β
β volumes β β NAS disk β (versioned) β S3 bucket β
β (Docker) β β + ZFS snaps β β (versioned) β
βββββββββββββββ ββββββββββββββββββββ βββββββββ¬ββββββββββ
β mc mirror /
β restic copy
βΌ
βββββββββββββββββββ
β Tier 3: Offsite β
β encrypted repo β
βββββββββββββββββββ
Tier 1 β Local fast target
Same-site disk (NAS or second machine). Purpose: absorb hourly/daily versioned snapshots with fast restore times (good RTO). Retention: keep 7 daily + 4 weekly. Cheap to keep dense because Tier 2 holds the long tail.
Tier 2 β Object storage (MinIO)
S3 API target running on different hardware than the primary (see minio-setup). Restic speaks S3 natively, so the local repo is replicated with restic copy or the repo is initialized directly on MinIO. Enable bucket versioning and a lifecycle rule to age out noncurrent versions β this gives you the "one copy on different infrastructure" property without a cloud bill.
Tier 3 β Offsite
An encrypted repository at a physically separate location. Because restic encrypts client-side, the offsite host never needs to be trusted. Sync Tier 2 β Tier 3 with rclone sync or a second restic remote, ideally from a different network path (e.g., over WireGuard VPN).
Encryption everywhere
Every tier beyond the service host must hold only ciphertext: restic repos are AES-256 encrypted with the repo password; database dumps should be piped through age or restic's stdin backup if stored raw. Keys/passwords belong in a secrets manager, not next to the backups (see sysadmin/secrets).
Practical Examples
Example 1: Classify a new service in 5 minutes
When adding any new Docker service, fill in this mini-template before writing a single backup command:
Service: jellyfin
Data: /srv/jellyfin/config (small), /media/library (large, re-creatable)
RPO: config 24h; library N/A (re-downloadable)
RTO: 4h
Model: versioned backup of config only; library excluded
Tiers: T1 restic local, T2 resticβMinIO, T3 inherited via repo replication
The key discipline: exclude re-creatable bulk data (media, caches) from all tiers. Backing up 4 TB of re-downloadable movies wastes every tier and stretches RTO.
Example 2: Consistent database snapshot feeding a versioned backup
#!/usr/bin/env bash
set -euo pipefail
# Tier 1: consistent dump, then restic picks it up
docker exec nextcloud-db pg_dump -U nextcloud nextcloud \
| gzip > /backup/staging/nextcloud-db-$(date +%F).sql.gz
restic backup /backup/staging /srv/nextcloud/data \
--tag nextcloud --tag auto
# Retention on Tier 1
restic forget --keep-daily 7 --keep-weekly 4 --prune
rm -f /backup/staging/nextcloud-db-*.sql.gz.tmp
Example 3: Replicating the repo to Tier 2
export RESTIC_REPOSITORY=s3:http://minio.internal:9000/backups
export AWS_ACCESS_KEY_ID=... AWS_SECRET_ACCESS_KEY=...
restic copy --from-repository /mnt/nas/restic --tag auto
Troubleshooting & Common Pitfalls
| Problem | Cause | Fix |
|---|---|---|
| Backups pass but restore fails | Never tested; repo corrupt or password lost | Schedule quarterly restore drills (see backup-to-object-storage) |
| Ransomware took out all copies | Sync-only "backups"; shares writable from desktops | Use versioned backups with append-only credentials; restrict SMB/NFS access to backup host |
| Offsite copy months stale | Manual offsite process | Automate Tier 3 sync inside the same timer chain (backup-automation) |
| Backup storage fills up | No retention policy; keeping forever | restic forget schedules per tier; monitor repo size (../observability/simple-alerts.md) |
| Database restore is inconsistent | Files backed up while DB running without snapshot | Dump first, or snapshot volume before file-level backup |
| Single disk = single copy illusion | Two "copies" on same RAID array | Verify tiers are on separate physical hosts/chassis |
Next Steps / Ops Actions
- Write down RPO/RTO for every running service using Example 1's template
- Implement Tier 2 with backup-to-object-storage
- Orchestrate the tier chain with schedules and alerts via backup-automation
- Review broader incident response in disaster-recovery
Sources & Related Articles
External references consulted:
Related knowledge-base articles:
Change Log
2026-08-26
- Initial creation by KB writing session.