---
title: Backups and restore testing
slug: backups-and-restore-testing
summary: How to check that every data store is backed up to policy, and how to prove it by restoring on a schedule.
order: 17
updated: 2026-11-11
status: ready
---

Use this page for a backup coverage audit, a disaster-recovery exercise, or before any risky data change. A backup you have never restored is a hope, not a backup.

## Define the policy per data class

Example starting point; replace the numbers with your own.

| Data class | RPO (max data loss) | RTO (max downtime) | Retention | Copy |
| --- | --- | --- | --- | --- |
| Production transactional DB | 5 min (point-in-time) | 1 h | 35 days plus monthly for 1 year | cross-region and cross-account |
| Production object storage | 24 h | 4 h | versioning plus lifecycle | cross-region replication |
| Non-production | 24 h | 1 day | 7 days | none |

Retention may be set by regulation. Check before shortening anything.

## Audit coverage with tags (read-only)

```bash
# Which RDS instances have automated backups and how long?
aws --profile <readonly-profile> --region <region> rds describe-db-instances \
  --query 'DBInstances[].{id:DBInstanceIdentifier,retention:BackupRetentionPeriod,encrypted:StorageEncrypted}' --output table

# What AWS Backup protects
aws --profile <readonly-profile> --region <region> backup list-protected-resources \
  --query 'Results[].{arn:ResourceArn,type:ResourceType,last:LastBackupTime}' --output table

# Buckets without versioning
for b in $(aws --profile <readonly-profile> s3api list-buckets --query 'Buckets[].Name' --output text); do
  v=$(aws --profile <readonly-profile> s3api get-bucket-versioning --bucket "$b" --query Status --output text 2>/dev/null)
  [ "$v" = "Enabled" ] || echo "$b versioning=$v"
done
```

- [ ] Every production data store appears in the protected list.
- [ ] A `backup` tag on each resource matches a backup plan selection rule.
- [ ] Backup vaults use a separate key and, ideally, a separate account with vault lock.

## Restore test runbook

Run at least quarterly per critical store.

1. Pick a recovery point (not the newest one; pick something a week old).
2. Restore into an isolated, non-production network with a new name.
3. Check: it starts, row counts or object counts are in the expected range, the application can read it.
4. Record the time it took. That is your real RTO.
5. Delete the restored copy.
6. Write the result in the ticket, with dates and durations.

```bash
# Example: restore an RDS snapshot into a test name (a write; needs approval)
aws --profile <restore-role-profile> --region <region> rds restore-db-instance-from-db-snapshot \
  --db-instance-identifier <db>-restore-test-$(date +%Y%m%d) \
  --db-snapshot-identifier <snapshot-id> \
  --db-subnet-group-name <isolated-subnet-group> --no-publicly-accessible
```

## Pitfalls

- Backups in the same account as production. A compromised account loses both.
- Encrypted backups whose KMS key can be deleted by the same admins.
- Restore tests that restore into production networks and accidentally connect to live consumers.
- Treating replication as backup. Replication copies deletes and corruption too.
