🛟 Cloud Backups and Disaster Recovery: Protect, Copy, Restore

Backup against snapshot against replication against disaster recovery, what each cloud's backup service offers (plans, vaults, retention, immutability, cross-region copies), how incremental backups and retention schedules really work (run for real), and why you only have a backup once you have restored one

💡 How this page was made. The mechanics were run for real on 6 October 2026 with GNU tar and Python, in a throwaway folder: a full backup and an incremental one, a restore chain checked with checksums, what a restore of only part of the chain gives, the retention schedule of a "gold" policy, and recovery-time arithmetic (the throughput figures there are illustrative, not measured on a cloud). The services and their settings are from the providers' documentation, read on 6 October 2026: AWS Backup, Azure Backup, Google Cloud snapshot schedules and Backup and DR, and Oracle block volume backups. Nothing was run on a cloud account. The same lesson, with a real two-machine routine and a destroyed-and-restored dataset, is on the FreeBSD Corner's ZFS backups page. Related: IAM, cost control, EC2 (snapshots) and Terraform.

Four words that get mixed up

Two numbers define what you need:

The arithmetic (as run; the transfer speeds are examples, not measurements):

>>> def worst_case_loss_hours(interval_hours, backup_minutes=20):
...     return interval_hours + backup_minutes / 60
... 
>>> for h in (24, 6, 1):
...     print('backup every %2d h: you can lose up to %.1f h of data' % (h, worst_case_loss_hours(h)))
... 
backup every 24 h: you can lose up to 24.3 h of data
backup every  6 h: you can lose up to 6.3 h of data
backup every  1 h: you can lose up to 1.3 h of data
>>> def restore_hours(size_gb, mb_per_s, fixed_minutes=15):
...     return size_gb * 1024 / mb_per_s / 3600 + fixed_minutes / 60
... 
>>> for size, speed in ((100, 100), (1000, 100), (1000, 400), (10000, 400)):
...     print('%5d GB at %3d MB/s -> about %.1f h to restore' % (size, speed, restore_hours(size, speed)))
... 
  100 GB at 100 MB/s -> about 0.5 h to restore
 1000 GB at 100 MB/s -> about 3.1 h to restore
 1000 GB at 400 MB/s -> about 1.0 h to restore
10000 GB at 400 MB/s -> about 7.4 h to restore

The 3-2-1 idea, in cloud terms

The classic rule: 3 copies of the data, on 2 kinds of storage, 1 off-site. In a cloud:

What each cloud offers

AWS: AWS Backup

A fully managed service that centralizes backups across AWS services. From the documentation:

Azure: Azure Backup

A native service for backing up VMs, disks, files, SQL Server, SAP HANA, PostgreSQL, MySQL, Cosmos DB, Azure Files, blobs, AKS and on-premises systems. From the documentation:

Google Cloud: snapshot schedules and Backup and DR

Two layers:

Oracle Cloud: block volume backup policies

For block and boot volumes, Oracle gives predefined policies that you cannot change, and user-defined ones:

What "incremental" really means (run for real)

A full backup copies everything. An incremental one copies only what changed since the last one, so it is small and fast. That is how every cloud snapshot and backup service keeps frequent backups affordable. The catch is on the restore side. I built a small folder (three reports, a 1.3 MB text file of numbers and a 3 MB random "photo") and made a full backup with GNU tar's incremental mode:

user@lab:~$ ls -lR data | grep -v "^total"
data:
drwxrwxr-x 2 user user    4096 Oct  6 19:18 docs
-rw-rw-r-- 1 user user 1288895 Oct  6 19:18 numbers.txt
-rw-rw-r-- 1 user user 3000000 Oct  6 19:18 photo.bin

data/docs:
-rw-rw-r-- 1 user user 40 Oct  6 19:18 report-1.txt
-rw-rw-r-- 1 user user 40 Oct  6 19:18 report-2.txt
-rw-rw-r-- 1 user user 40 Oct  6 19:18 report-3.txt
user@lab:~$ (cd data && find . -type f | sort | xargs sha256sum) > before-1.sha; wc -l before-1.sha
5 before-1.sha
user@lab:~$ tar --listed-incremental=state.snar -czf level0.tgz data; ls -l level0.tgz
-rw-rw-r-- 1432203 Oct  6 19:18 level0.tgz

Then I changed the data: edited one report, added a new one, deleted another, and made the second backup:

user@lab:~$ echo "REVISED" >> data/docs/report-2.txt; echo new > data/docs/report-4.txt; rm data/docs/report-3.txt
user@lab:~$ (cd data && find . -type f | sort | xargs sha256sum) > before-2.sha
user@lab:~$ tar --listed-incremental=state.snar -czf level1.tgz data; ls -l level0.tgz level1.tgz
-rw-rw-r-- 1432203 Oct  6 19:18 level0.tgz
-rw-rw-r-- 1   304 Oct  6 19:18 level1.tgz
user@lab:~$ tar -tzf level1.tgz | grep -v "/$"
data/docs/report-2.txt
data/docs/report-4.txt
user@lab:~$ tar -tzvf level1.tgz | cut -c1-12,30-
drwxrwxr-x use 19:18 data/
drwxrwxr-x use 19:18 data/docs/
-rw-rw-r-- use 19:18 data/docs/report-2.txt
-rw-rw-r-- use 19:18 data/docs/report-4.txt

Now the restores, and the part people forget:

user@lab:~$ mkdir r-l0; tar --listed-incremental=/dev/null -xzf level0.tgz -C r-l0   # restore only the first backup
user@lab:~$ (cd r-l0/data && find . -type f | sort | xargs sha256sum) | diff - before-2.sha > /dev/null && echo IDENTICAL || echo 'DIFFERENT: the first backup alone is out of date'
DIFFERENT: the first backup alone is out of date
user@lab:~$ mkdir r-l1; tar --listed-incremental=/dev/null -xzf level1.tgz -C r-l1   # restore only the incremental
user@lab:~$ find r-l1 -type f
r-l1/data/docs/report-2.txt
r-l1/data/docs/report-4.txt
user@lab:~$ mkdir r-full; tar --listed-incremental=/dev/null -xzf level0.tgz -C r-full; tar --listed-incremental=/dev/null -xzf level1.tgz -C r-full   # the chain, in order
user@lab:~$ (cd r-full/data && find . -type f | sort | xargs sha256sum) | diff - before-2.sha && echo IDENTICAL: the chain restores the state of the second backup
IDENTICAL: the chain restores the state of the second backup
user@lab:~$ ls r-full/data/docs   # report-3.txt was deleted after the first backup
report-1.txt
report-2.txt
report-4.txt

Retention: how long, and what you can go back to

A retention policy decides which restore points exist. A common design is grandfather-father-son: many recent points, fewer older ones. Oracle's documented Gold policy (7 daily, 4 weekly, 12 monthly and 5 yearly) as a model, run for real:

>>> import datetime as dt
>>> today = dt.date(2026, 10, 6)
>>> days = [today - dt.timedelta(d) for d in range(0, 365 * 6)]
>>> keep = set()
>>> keep |= {d for d in days if (today - d).days < 7}
>>> sundays = [d for d in days if d.weekday() == 6]
>>> keep |= set(sundays[:4])
>>> firsts = [d for d in days if d.day == 1]
>>> keep |= set(firsts[:12])
>>> januaries = [d for d in firsts if d.month == 1]
>>> keep |= set(januaries[:5])
>>> len(keep)
25
>>> sorted(keep)[0], sorted(keep)[-1]
(datetime.date(2022, 1, 1), datetime.date(2026, 10, 6))
>>> def newest_before(target): return max(d for d in keep if d <= target)
... 
>>> newest_before(dt.date(2026, 8, 20))
datetime.date(2026, 8, 1)
>>> newest_before(dt.date(2025, 3, 15))
datetime.date(2025, 1, 1)
>>> newest_before(dt.date(2023, 6, 30))
datetime.date(2023, 1, 1)
>>> (dt.date(2023, 6, 30) - newest_before(dt.date(2023, 6, 30))).days
180

Protect the backups themselves

Attackers and mistakes target backups too. What the documentation gives you:

The restore test (run for real)

The single most important habit: restore, and compare. The checks used above are small enough to automate:

(cd ORIGINAL && find . -type f | sort | xargs sha256sum) > before.sha
# ... restore into a new, empty place ...
(cd RESTORED && find . -type f | sort | xargs sha256sum) | diff - before.sha && echo IDENTICAL

A restore test worth doing, for each important system, at least twice a year:

  1. Restore to a new place (a new volume, a new database, a test account), never over the original.
  2. Compare: checksums of files, row counts or a known query of the database, a few real application checks.
  3. Time it, and compare with your RTO. AWS Backup has restore testing as a feature (and bills for it).
  4. Write down what you needed: keys, passwords, the order of steps, who has access. A backup you cannot decrypt, or restore because the one person with the key is away, is not a backup.
  5. Fix what you find, and test again.

Disaster recovery in one page

When a whole region (or account) is gone, the question is how quickly the service runs somewhere else. Common levels, from cheap and slow to expensive and fast:

The tools named in the documentation: cross-region copies (AWS Backup, Oracle's copy policy, Google's cross-region clones, Azure's GRS vaults), and Azure Site Recovery for failover. And the infrastructure should be code (Terraform, first-boot files: cloud-init page) so that rebuilding the environment is a command and not a memory.

Common mistakes

What this page did not cover

Database-specific backup and point-in-time recovery, application-consistent backups in depth, backing up Kubernetes state (AKS and EKS backups exist and are named above), SaaS data (email, documents), ransomware response plans, and prices (read each provider's pricing page).

The cheat sheet

← First Boot on Every Cloud: cloud-init and User Data · ↑ All tutorials · Cloud Security Baseline: A Checklist You Can Audit →