[{"data":1,"prerenderedAt":415},["ShallowReactive",2],{"blog-best-practices/backup":3},{"id":4,"title":5,"body":6,"category":391,"date":392,"dateModified":392,"description":393,"draft":394,"extension":395,"faq":396,"featured":394,"headerVariant":400,"image":401,"keywords":401,"meta":402,"navigation":403,"ogDescription":404,"ogTitle":401,"path":405,"readTime":406,"schemaOrg":407,"schemaType":408,"seo":409,"sitemap":410,"stem":411,"tags":412,"twitterCard":413,"__hash__":414},"blog/blog/best-practices/backup.md","Backup and Recovery Best Practices: Data Protection and Disaster Recovery",{"type":7,"value":8,"toc":380},"minimark",[9,20,29,34,37,59,74,78,81,137,146,150,153,162,179,183,186,225,234,238,241,250,254,257,266,272,300,322,326,329,349,368],[10,11,12],"tldr",{},[13,14,15,19],"p",{},[16,17,18],"strong",{},"The #1 backup security best practice is following the 3-2-1 rule: 3 copies, 2 different media types, 1 offsite."," Encrypt backups at rest and in transit. Test restores regularly (untested backups are not backups). Automate backup verification and set up alerts for failures. Document your recovery procedures.",[21,22,23],"quotable-box",{},[24,25,26],"blockquote",{},[13,27,28],{},"\"A backup you haven't tested is just a hope. A backup you've restored is a guarantee.\"",[30,31,33],"h2",{"id":32},"best-practice-1-the-3-2-1-backup-rule-5-min","Best Practice 1: The 3-2-1 Backup Rule 5 min",[13,35,36],{},"A proven strategy for data protection:",[38,39,40,47,53],"ul",{},[41,42,43,46],"li",{},[16,44,45],{},"3 copies"," of your data (production + 2 backups)",[41,48,49,52],{},[16,50,51],{},"2 different media types"," (database + object storage)",[41,54,55,58],{},[16,56,57],{},"1 offsite location"," (different region or provider)",[60,61,63],"code-block",{"label":62},"PostgreSQL backup strategy example",[64,65,70],"pre",{"className":66,"code":68,"language":69},[67],"language-text","# Automated backup script\n#!/bin/bash\nset -e\n\nDATE=$(date +%Y-%m-%d-%H%M)\nBACKUP_FILE=\"backup-${DATE}.sql.gz\"\n\n# Create encrypted backup\npg_dump $DATABASE_URL | gzip | \\\n  gpg --symmetric --cipher-algo AES256 \\\n      --passphrase-file /secrets/backup-key \\\n      --batch -o \"/backups/${BACKUP_FILE}.gpg\"\n\n# Upload to primary storage (same region)\naws s3 cp \"/backups/${BACKUP_FILE}.gpg\" \\\n  \"s3://backups-primary/${BACKUP_FILE}.gpg\" \\\n  --storage-class STANDARD_IA\n\n# Upload to offsite storage (different region)\naws s3 cp \"/backups/${BACKUP_FILE}.gpg\" \\\n  \"s3://backups-offsite/${BACKUP_FILE}.gpg\" \\\n  --region eu-west-1 \\\n  --storage-class GLACIER\n\n# Verify backup integrity\naws s3api head-object \\\n  --bucket backups-primary \\\n  --key \"${BACKUP_FILE}.gpg\"\n\necho \"Backup completed: ${BACKUP_FILE}\"\n","text",[71,72,68],"code",{"__ignoreMap":73},"",[30,75,77],{"id":76},"best-practice-2-encrypt-all-backups-10-min","Best Practice 2: Encrypt All Backups 10 min",[13,79,80],{},"Backups are high-value targets for attackers:",[82,83,84,100],"table",{},[85,86,87],"thead",{},[88,89,90,94,97],"tr",{},[91,92,93],"th",{},"Encryption Type",[91,95,96],{},"When to Use",[91,98,99],{},"Key Management",[101,102,103,115,126],"tbody",{},[88,104,105,109,112],{},[106,107,108],"td",{},"Server-side (SSE-S3)",[106,110,111],{},"Basic protection",[106,113,114],{},"AWS managed",[88,116,117,120,123],{},[106,118,119],{},"Server-side (SSE-KMS)",[106,121,122],{},"Audit requirements",[106,124,125],{},"KMS with rotation",[88,127,128,131,134],{},[106,129,130],{},"Client-side",[106,132,133],{},"Maximum security",[106,135,136],{},"You manage keys",[60,138,140],{"label":139},"Client-side encryption before upload",[64,141,144],{"className":142,"code":143,"language":69},[67],"import { createCipheriv, randomBytes } from 'crypto';\nimport { pipeline } from 'stream/promises';\nimport { createGzip } from 'zlib';\n\nasync function encryptBackup(inputStream, outputPath) {\n  // Generate unique key for this backup\n  const key = randomBytes(32);\n  const iv = randomBytes(16);\n\n  // Store key securely (e.g., in Secrets Manager)\n  await storeBackupKey(outputPath, { key, iv });\n\n  const cipher = createCipheriv('aes-256-gcm', key, iv);\n  const gzip = createGzip();\n\n  await pipeline(\n    inputStream,\n    gzip,\n    cipher,\n    fs.createWriteStream(outputPath)\n  );\n\n  // Return auth tag for verification\n  return cipher.getAuthTag();\n}\n",[71,145,143],{"__ignoreMap":73},[30,147,149],{"id":148},"best-practice-3-test-restores-regularly-15-min","Best Practice 3: Test Restores Regularly 15 min",[13,151,152],{},"A backup you have not tested is not a backup:",[60,154,156],{"label":155},"Automated restore testing",[64,157,160],{"className":158,"code":159,"language":69},[67],"# Weekly restore test script\n#!/bin/bash\nset -e\n\necho \"Starting restore test...\"\n\n# Get latest backup\nLATEST=$(aws s3 ls s3://backups-primary/ | sort | tail -1 | awk '{print $4}')\n\n# Download and decrypt\naws s3 cp \"s3://backups-primary/${LATEST}\" /tmp/restore-test.gpg\ngpg --decrypt --passphrase-file /secrets/backup-key \\\n    --batch /tmp/restore-test.gpg | gunzip > /tmp/restore-test.sql\n\n# Restore to test database\ncreatedb restore_test_db\npsql restore_test_db \u003C /tmp/restore-test.sql\n\n# Run verification queries\nUSERS=$(psql restore_test_db -t -c \"SELECT count(*) FROM users\")\nORDERS=$(psql restore_test_db -t -c \"SELECT count(*) FROM orders\")\n\n# Compare with production counts (within 1% tolerance)\nPROD_USERS=$(psql $DATABASE_URL -t -c \"SELECT count(*) FROM users\")\nif [ $(echo \"$USERS \u003C $PROD_USERS * 0.99\" | bc) -eq 1 ]; then\n  echo \"ALERT: User count mismatch\"\n  exit 1\nfi\n\n# Cleanup\ndropdb restore_test_db\nrm /tmp/restore-test.*\n\necho \"Restore test passed!\"\necho \"Users: ${USERS}, Orders: ${ORDERS}\"\n",[71,161,159],{"__ignoreMap":73},[38,163,164,167,170,173,176],{},[41,165,166],{},"Test full restores monthly",[41,168,169],{},"Test partial restores (single table) weekly",[41,171,172],{},"Measure restore time (RTO)",[41,174,175],{},"Verify data integrity after restore",[41,177,178],{},"Document any issues found",[30,180,182],{"id":181},"best-practice-4-define-rto-and-rpo-10-min","Best Practice 4: Define RTO and RPO 10 min",[13,184,185],{},"Know your recovery requirements:",[82,187,188,201],{},[85,189,190],{},[88,191,192,195,198],{},[91,193,194],{},"Metric",[91,196,197],{},"Definition",[91,199,200],{},"Typical Values",[101,202,203,214],{},[88,204,205,208,211],{},[106,206,207],{},"RPO (Recovery Point Objective)",[106,209,210],{},"Maximum acceptable data loss",[106,212,213],{},"1 hour to 24 hours",[88,215,216,219,222],{},[106,217,218],{},"RTO (Recovery Time Objective)",[106,220,221],{},"Maximum acceptable downtime",[106,223,224],{},"15 min to 4 hours",[60,226,228],{"label":227},"Backup frequency based on RPO",[64,229,232],{"className":230,"code":231,"language":69},[67],"# RPO: 1 hour = Backup every hour\n0 * * * * /scripts/backup.sh\n\n# RPO: 15 minutes = Use continuous replication\n# PostgreSQL: streaming replication to standby\n# AWS RDS: Enable automated backups with PITR\n\n# RPO: Near-zero = Multi-region active-active\n# Use database replication + application-level sync\n\n# Terraform: RDS with point-in-time recovery\nresource \"aws_db_instance\" \"main\" {\n  backup_retention_period = 7\n  backup_window           = \"03:00-04:00\"\n\n  # Enable PITR for RPO of ~5 minutes\n  enabled_cloudwatch_logs_exports = [\"postgresql\"]\n\n  # Multi-AZ for high availability\n  multi_az = true\n}\n",[71,233,231],{"__ignoreMap":73},[30,235,237],{"id":236},"best-practice-5-secure-backup-access-15-min","Best Practice 5: Secure Backup Access 15 min",[13,239,240],{},"Limit who can access or delete backups:",[60,242,244],{"label":243},"S3 bucket policy for backup protection",[64,245,248],{"className":246,"code":247,"language":69},[67],"{\n  \"Version\": \"2012-10-17\",\n  \"Statement\": [\n    {\n      \"Sid\": \"DenyDeleteExceptBackupAdmin\",\n      \"Effect\": \"Deny\",\n      \"Principal\": \"*\",\n      \"Action\": [\n        \"s3:DeleteObject\",\n        \"s3:DeleteObjectVersion\"\n      ],\n      \"Resource\": \"arn:aws:s3:::backups-primary/*\",\n      \"Condition\": {\n        \"StringNotEquals\": {\n          \"aws:PrincipalArn\": \"arn:aws:iam::123456789:role/BackupAdmin\"\n        }\n      }\n    },\n    {\n      \"Sid\": \"RequireMFAForDelete\",\n      \"Effect\": \"Deny\",\n      \"Principal\": \"*\",\n      \"Action\": \"s3:DeleteObject\",\n      \"Resource\": \"arn:aws:s3:::backups-primary/*\",\n      \"Condition\": {\n        \"Bool\": {\n          \"aws:MultiFactorAuthPresent\": \"false\"\n        }\n      }\n    }\n  ]\n}\n\n// Enable Object Lock for immutable backups\n// (ransomware protection)\naws s3api put-object-lock-configuration \\\n  --bucket backups-primary \\\n  --object-lock-configuration '{\n    \"ObjectLockEnabled\": \"Enabled\",\n    \"Rule\": {\n      \"DefaultRetention\": {\n        \"Mode\": \"GOVERNANCE\",\n        \"Days\": 30\n      }\n    }\n  }'\n",[71,249,247],{"__ignoreMap":73},[30,251,253],{"id":252},"best-practice-6-monitor-backup-health-10-min","Best Practice 6: Monitor Backup Health 10 min",[13,255,256],{},"Set up alerts for backup failures:",[60,258,260],{"label":259},"Backup monitoring and alerting",[64,261,264],{"className":262,"code":263,"language":69},[67],"// Check backup age\nasync function checkBackupHealth() {\n  const backups = await s3.listObjects({\n    Bucket: 'backups-primary',\n    Prefix: 'backup-',\n  });\n\n  const latest = backups.Contents\n    .sort((a, b) => b.LastModified - a.LastModified)[0];\n\n  const ageHours = (Date.now() - latest.LastModified) / (1000 * 60 * 60);\n\n  if (ageHours > 25) {  // More than 1 day old\n    await sendAlert({\n      severity: 'critical',\n      message: `Latest backup is ${ageHours.toFixed(1)} hours old`,\n      runbook: 'https://wiki/runbooks/backup-failure',\n    });\n  }\n\n  // Check backup size (detect empty or truncated backups)\n  if (latest.Size \u003C 1000000) {  // Less than 1MB\n    await sendAlert({\n      severity: 'critical',\n      message: `Backup suspiciously small: ${latest.Size} bytes`,\n    });\n  }\n}\n\n// Run hourly\nsetInterval(checkBackupHealth, 60 * 60 * 1000);\n",[71,265,263],{"__ignoreMap":73},[267,268,269],"info-box",{},[13,270,271],{},"Ransomware Protection:\nUse immutable backups (S3 Object Lock, Azure Immutable Blob) to prevent ransomware from encrypting or deleting your backups. Keep at least one backup copy completely air-gapped or on a different cloud provider.",[267,273,274],{},[13,275,276,279,280,287,288,293,294,299],{},[16,277,278],{},"Official Resources:"," For comprehensive backup and disaster recovery guidance, see ",[281,282,286],"a",{"href":283,"rel":284},"https://docs.aws.amazon.com/prescriptive-guidance/latest/backup-recovery/welcome.html",[285],"nofollow","AWS Backup and Recovery Prescriptive Guidance",", ",[281,289,292],{"href":290,"rel":291},"https://cloud.google.com/architecture/dr-scenarios-planning-guide",[285],"Google Cloud Disaster Recovery Planning Guide",", and ",[281,295,298],{"href":296,"rel":297},"https://learn.microsoft.com/en-us/azure/backup/backup-overview",[285],"Azure Backup Documentation",".",[301,302,303,310,316],"faq-section",{},[304,305,307],"faq-item",{"question":306},"How long should I retain backups?",[13,308,309],{},"Keep daily backups for 7-30 days, weekly backups for 3 months, and monthly backups for 1-7 years depending on compliance requirements. Use lifecycle policies to automatically transition to cheaper storage tiers.",[304,311,313],{"question":312},"Should I back up my Supabase/Firebase database?",[13,314,315],{},"Yes. While managed services have their own backups, you should maintain independent backups you control. Export data regularly and store it in your own cloud storage. This protects against account issues and vendor lock-in.",[304,317,319],{"question":318},"What about backing up file uploads?",[13,320,321],{},"Enable versioning on your storage bucket and replicate to a secondary region. For critical files, consider cross-cloud replication. Test that you can restore specific file versions, not just the latest.",[30,323,325],{"id":324},"further-reading","Further Reading",[13,327,328],{},"Put these practices into action with our step-by-step guides.",[38,330,331,337,343],{},[41,332,333],{},[281,334,336],{"href":335},"/blog/how-to/add-security-headers","Add security headers to your app",[41,338,339],{},[281,340,342],{"href":341},"/blog/checklists/pre-deployment-security-checklist","Pre-deployment security checklist",[41,344,345],{},[281,346,348],{"href":347},"/blog/getting-started/first-scan","Run your first security scan",[350,351,352,358,363],"related-articles",{},[353,354],"related-card",{"description":355,"href":356,"title":357},"Secure database practices","/blog/best-practices/database","Database Security",[353,359],{"description":360,"href":361,"title":362},"Secure deployments","/blog/best-practices/deployment","Deployment Security",[353,364],{"description":365,"href":366,"title":367},"Supabase backup and RLS","/blog/best-practices/supabase","Supabase Security",[369,370,373,377],"cta-box",{"href":371,"label":372},"/","Start Free Scan",[30,374,376],{"id":375},"verify-your-backup-strategy","Verify Your Backup Strategy",[13,378,379],{},"Check your backup configuration and recovery procedures.",{"title":73,"searchDepth":381,"depth":381,"links":382},2,[383,384,385,386,387,388,389,390],{"id":32,"depth":381,"text":33},{"id":76,"depth":381,"text":77},{"id":148,"depth":381,"text":149},{"id":181,"depth":381,"text":182},{"id":236,"depth":381,"text":237},{"id":252,"depth":381,"text":253},{"id":324,"depth":381,"text":325},{"id":375,"depth":381,"text":376},"best-practices","2026-01-20","Backup and recovery best practices. Learn secure backup strategies, encryption, testing procedures, and disaster recovery planning for applications.",false,"md",[397,398,399],{"question":306,"answer":309},{"question":312,"answer":315},{"question":318,"answer":321},"vibe-green",null,{},true,"Implement secure backup strategies and disaster recovery procedures.","/blog/best-practices/backup","11 min read","[object Object]","Article",{"title":5,"description":393},{"loc":405},"blog/best-practices/backup",[],"summary_large_image","-9k2aHkhfHFJKBWzPxEeL4Z4o-rrYT6O9I2kcWo-p4k",1775843926288]