Every small cloud team is one failed disk, one botched migration, or one ransomware incident away from a catastrophic data loss. A backup and disaster recovery plan is not optional -- it is the difference between a two-hour recovery and a company-ending event. This guide covers the 3-2-1 backup rule, RPO/RTO target-setting, automated backup scripts, restore testing, cross-region replication, disaster recovery drills, and communication plans sized for teams of 2-15 engineers.
Understanding RPO and RTO
Before choosing any backup tool or strategy, your team must define two numbers for each service tier:
- RPO (Recovery Point Objective) -- the maximum acceptable data loss measured in time. An RPO of 1 hour means you can lose at most 1 hour of data.
- RTO (Recovery Time Objective) -- the maximum acceptable downtime. An RTO of 30 minutes means the service must be back online within 30 minutes of an incident.
These targets drive every other decision. Here is a practical tier framework:
| Service tier | Examples | RPO target | RTO target | Backup frequency |
| Tier 1 - Critical | Primary database, auth service | 5 minutes | 15 minutes | Continuous WAL + hourly snapshots |
| Tier 2 - Important | API servers, cache, queues | 1 hour | 1 hour | Hourly snapshots |
| Tier 3 - Standard | Internal tools, staging | 24 hours | 4 hours | Daily snapshots |
| Tier 4 - Non-critical | Dev environments, logs | 7 days | 24 hours | Weekly snapshots |
Review your service inventory in Services Overview and assign each service a tier before proceeding.
The 3-2-1 Backup Rule
The 3-2-1 rule is the foundation of every reliable backup strategy:
- 3 copies of your data (1 primary + 2 backups).
- 2 different storage media or platforms.
- 1 copy offsite (different geographic region or provider).
For cloud-native teams, this translates to:
Extended best practices:
- Add a 3-2-1-1 layer: one copy should be immutable (write-once, cannot be deleted or modified for a retention period). This protects against ransomware.
- Use object lock or WORM (Write Once Read Many) features available in S3, GCS, and Azure Blob.
Automated Backup Scripts
PostgreSQL Backup with pg_dump and S3
#!/bin/bash
<h1>backup-postgres.sh -- Automated PostgreSQL backup to S3</h1>
set -euo pipefail
DB_NAME="${DB_NAME:-appdb}"
DB_HOST="${DB_HOST:-localhost}"
DB_USER="${DB_USER:-app}"
S3_BUCKET="${S3_BUCKET:-my-backups}"
TIMESTAMP=$(date -u +"%Y%m%dT%H%M%SZ")
BACKUP_FILE="/tmp/${DB_NAME}_${TIMESTAMP}.sql.gz"
<h1>Create compressed backup</h1>
pg_dump -h "$DB_HOST" -U "$DB_USER" -d "$DB_NAME" | gzip > "$BACKUP_FILE"
<h1>Upload to S3 with server-side encryption</h1>
aws s3 cp "$BACKUP_FILE" "s3://${S3_BUCKET}/postgres/${DB_NAME}/${TIMESTAMP}.sql.gz" \
--sse AES256
<h1>Upload to cross-region bucket (3-2-1 offsite copy)</h1>
aws s3 cp "$BACKUP_FILE" "s3://${S3_BUCKET}-dr/postgres/${DB_NAME}/${TIMESTAMP}.sql.gz" \
--sse AES256 --region us-west-2
<h1>Cleanup local file</h1>
rm -f "$BACKUP_FILE"
<h1>Prune backups older than 30 days from primary bucket</h1>
aws s3 ls "s3://${S3_BUCKET}/postgres/${DB_NAME}/" | \
awk "{print \$4}" | while read -r key; do
file_date=$(echo "$key" | grep -oP "\d{8}")
cutoff=$(date -u -d "30 days ago" +"%Y%m%d")
if [[ "$file_date" < "$cutoff" ]]; then
aws s3 rm "s3://${S3_BUCKET}/postgres/${DB_NAME}/${key}"
fi
done
echo "Backup completed: ${TIMESTAMP}"
Continuous WAL Archiving for Tier 1 Services
For critical databases where 5-minute RPO matters, use continuous WAL (Write-Ahead Log) archiving:
<h1>postgresql.conf settings for WAL archiving</h1>
archive_mode = on
archive_command = 'aws s3 cp %p s3://my-backups/wal/%f --sse AES256'
archive_timeout = 300 # Force archive every 5 minutes
This lets you restore to any point in time within your retention window, not just to the last snapshot.
Cron Schedule for Automated Backups
<h1>/etc/cron.d/backups</h1>
<h1>Tier 1: Every hour</h1>
0 * * * * backup-user /opt/scripts/backup-postgres.sh >> /var/log/backup.log 2>&1
<h1>Tier 2: Every 6 hours</h1>
0 */6 * * * backup-user /opt/scripts/backup-redis.sh >> /var/log/backup.log 2>&1
<h1>Tier 3: Daily at 2 AM UTC</h1>
0 2 * * * backup-user /opt/scripts/backup-staging.sh >> /var/log/backup.log 2>&1
<h1>Weekly integrity check (verify backup can be restored)</h1>
0 4 * * 0 backup-user /opt/scripts/verify-restore.sh >> /var/log/backup.log 2>&1
Restore Testing Procedures
A backup that has never been restored is not a backup -- it is a hope. Restore testing must be scheduled and documented:
Automated Restore Verification Script
#!/bin/bash
<h1>verify-restore.sh -- Restore latest backup and validate</h1>
set -euo pipefail
LATEST=$(aws s3 ls "s3://${S3_BUCKET}/postgres/appdb/"
sort tail -1
awk "{print \$4}")
TEST_DB="restore_test_$(date +%s)"
<h1>Download and restore</h1>
aws s3 cp "s3://${S3_BUCKET}/postgres/appdb/${LATEST}" /tmp/restore.sql.gz
gunzip -c /tmp/restore.sql.gz | psql -h localhost -U app -d "$TEST_DB"
<h1>Validate row counts against production</h1>
PROD_USERS=$(psql -h prod-host -U app -d appdb -t -c "SELECT count(*) FROM users")
TEST_USERS=$(psql -h localhost -U app -d "$TEST_DB" -t -c "SELECT count(*) FROM users")
if [[ "$PROD_USERS" -ne "$TEST_USERS" ]]; then
echo "RESTORE VALIDATION FAILED: user count mismatch (prod: $PROD_USERS, test: $TEST_USERS)"
exit 1
fi
<h1>Cleanup test database</h1>
psql -h localhost -U app -c "DROP DATABASE ${TEST_DB}"
rm -f /tmp/restore.sql.gz
echo "Restore verification passed: ${LATEST}"
Restore Testing Schedule
| Test type | Frequency | Duration | Who runs it |
| Automated restore check | Weekly | 10-15 min (automated) | Cron job |
| Manual point-in-time restore | Monthly | 1-2 hours | On-call engineer |
| Full failover drill | Quarterly | 2-4 hours | Whole team |
| Cross-region recovery | Semiannually | 4-8 hours | Whole team + stakeholders |
Cross-Region Replication
For Tier 1 services, cross-region replication reduces RTO by keeping a warm standby ready:
<h1>S3 cross-region replication rule (via AWS CLI)</h1>
aws s3api put-bucket-replication \
--bucket my-backups \
--replication-configuration '{
"Role": "arn:aws:iam::role/s3-replication",
"Rules": [{
"Status": "Enabled",
"Destination": {
"Bucket": "arn:aws:s3:::my-backups-dr",
"StorageClass": "STANDARD_IA"
}
}]
}'
For database-level replication, use streaming replication (PostgreSQL) or managed read replicas in a secondary region. Review infrastructure options at Cloud Computing.
Disaster Recovery Drills
Drills are the only way to know if your plan actually works. Small teams should run three types:
1. Tabletop Drill (Monthly, 30 minutes)
Walk through a scenario verbally without touching production:
- "The primary database is corrupted. Walk me through recovery step by step."
- "Our cloud provider has a region-wide outage. What do we do in the first 15 minutes?"
- Identify gaps in runbooks, missing credentials, unclear ownership.
2. Technical Restore Drill (Quarterly, 2-4 hours)
Actually restore from backup to a test environment:
- Restore the database from the latest backup.
- Verify application functionality against the restored data.
- Measure actual RTO and compare against target.
- Document every step that was harder than expected.
3. Full Failover Simulation (Semiannually, 4-8 hours)
Simulate a complete region failure and recover in a secondary region:
- Switch DNS to failover endpoints.
- Restore services from cross-region backups.
- Run application tests against the failover environment.
- Measure actual RTO and RPO achieved.
Communication Plan
During an incident, communication failures cause as much damage as technical failures. Define these in advance:
INCIDENT COMMUNICATION TEMPLATE
Subject: [SEV-{1
2
3}] {Service} - {Brief description}
Status: Investigating / Identified / Monitoring / Resolved
Impact: {Who is affected and how}
Start time: {UTC timestamp}
Next update: {UTC timestamp, within 30 min for SEV1}
What happened:
{2-3 sentences}
What we are doing:
{Current actions}
Customer action needed:
{None / specific instructions}
Communication channels by severity:
| Severity | Internal channel | Customer channel | Update frequency |
| SEV1 - Critical | War room + phone tree | Status page + email | Every 15 min |
| SEV2 - Major | Incident Slack channel | Status page | Every 30 min |
| SEV3 - Minor | Team Slack channel | None (internal) | Every 2 hours |
30-Day Starter Plan
If your team has no DR plan today, follow this 30-day roadmap:
Check your current infrastructure at Services Overview and benchmark backup costs at Pricing.
Frequently Asked Questions
How much should a small team spend on backup infrastructure?
Budget 5-15% of your total infrastructure spend on backups and DR. For a team spending $500/month on cloud, that is $25-75/month on backup storage and tooling. Object storage (S3, GCS) is cheap -- most teams spend more on the engineering time to set up and test than on the storage itself.
Is cloud provider snapshots enough for disaster recovery?
Cloud snapshots are a good start but not sufficient alone. Snapshots are typically stored in the same region as the source. A region-wide outage would take out both your data and your snapshots. Always have at least one copy in a different region or provider.
How often should we actually test restores?
Automated restore verification should run weekly. Manual point-in-time restore tests should happen monthly. Full failover drills should happen quarterly. If you are not testing restores at least monthly, your backups are aspirational, not operational.
What is the difference between backup and disaster recovery?
Backup is copying data to a safe location. Disaster recovery is the entire plan for getting your services running again after a failure, which includes backups but also covers networking, DNS failover, application deployment, data validation, and team communication. You need both.
Should we use a managed backup service or build our own?
Start with managed services (cloud provider snapshots, managed database backups) for speed. Add custom scripts only for gaps the managed service does not cover, such as cross-provider copies or application-level consistency checks. Building everything from scratch is rarely justified for teams under 10 engineers.
How do we handle backup encryption?
Encrypt all backups at rest using server-side encryption (SSE-S3 or SSE-KMS). For cross-provider copies, encrypt client-side before upload using GPG or age. Store encryption keys separately from the backups -- ideally in a different provider or a hardware security module.
Final Takeaway
A disaster recovery plan for small teams does not need enterprise complexity. It needs clear RPO/RTO targets, automated backups with verified restores, a communication template, and regular drills. The gap between "we have backups" and "we can recover" is measured entirely in testing. Start your 30-day plan today.
Need tailored guidance for your stack? Contact us.