Meera builds the backup system nobody questioned
tar, rsync, pg_dump, rclone — backups that actually work when you need them
Meera woke up on a Wednesday morning to find the production database had been accidentally dropped. A developer ran DROP DATABASE on production instead of staging. Everyone had assumed backups existed. Nobody had verified they actually worked.
Meera spent the next 8 hours rebuilding the database from application logs and whatever data she could find. The company lost 6 hours of transaction data.
The next morning she built a proper backup system. Three months later she tested it. It restored in 12 minutes. That test saved the company when the database server's SSD failed 6 weeks after the test.
THE BACKUP RULES MEERA FOLLOWED
Rule 1: 3-2-1 backup strategy.
3 copies of data. 2 different storage media. 1 offsite copy.
Rule 2: A backup you have never tested is not a backup.
Test restore every month.
Rule 3: Automate everything. Manual backups get forgotten.
BACKING UP FILES — TAR
tar is the standard Linux archiving tool. It bundles files into a single archive, optionally compressed.
# Create a compressed backup:
tar -czf backup_$(date +%Y%m%d).tar.gz /opt/app/config/ /opt/app/data/# What the flags mean:
# c = create, z = gzip compress, f = filename# List contents without extracting:
tar -tzf backup_20260316.tar.gz# Extract to a specific directory:
tar -xzf backup_20260316.tar.gz -C /restore/# Incremental backup (only changed files since last backup):
tar -czf incremental_$(date +%Y%m%d).tar.gz \
--newer-mtime="2026-03-15" \
/opt/app/data/# Exclude directories:
tar -czf backup.tar.gz /opt/app/ --exclude=/opt/app/logs --exclude=/opt/app/tmpRSYNC — SMARTER FILE SYNC AND BACKUP
rsync only transfers what changed. Ideal for large directories where most files do not change daily.
# Sync a directory to a backup location:
rsync -avz /opt/app/ /backup/app/
# a = archive (preserves permissions, timestamps, symlinks)
# v = verbose
# z = compress during transfer# Sync to a remote server:
rsync -avz /opt/app/ backup-server:/backup/app/# Mirror exactly (delete files on destination that no longer exist on source):
rsync -avz --delete /opt/app/ backup-server:/backup/app/# Dry run (show what would happen without doing it):
rsync -avzn --delete /opt/app/ backup-server:/backup/app/# Exclude certain files:
rsync -avz --exclude='*.log' --exclude='tmp/' /opt/app/ /backup/app/POSTGRESQL BACKUPS
# Dump a single database:
pg_dump dbname > backup_$(date +%Y%m%d).sql# Compressed dump:
pg_dump dbname | gzip > backup_$(date +%Y%m%d).sql.gz# Dump all databases:
pg_dumpall > all_databases_$(date +%Y%m%d).sql# Restore from dump:
psql dbname < backup_20260316.sql
# or compressed:
gunzip -c backup_20260316.sql.gz | psql dbname# Custom format (faster restore, allows parallel restore):
pg_dump -Fc dbname > backup_$(date +%Y%m%d).dump
pg_restore -d dbname backup_20260316.dump# Automated daily backup in cron:
0 2 * * * postgres pg_dump appdb | gzip > /backups/appdb_$(date +\%Y\%m\%d).sql.gz && find /backups -name "*.gz" -mtime +30 -deleteMYSQL BACKUPS
# Dump a database:
mysqldump -u root -p dbname > backup_$(date +%Y%m%d).sql# Compressed:
mysqldump -u root -p dbname | gzip > backup_$(date +%Y%m%d).sql.gz# Restore:
mysql -u root -p dbname < backup_20260316.sql# All databases:
mysqldump -u root -p --all-databases > all_$(date +%Y%m%d).sqlTESTING YOUR RESTORE — THE MOST IMPORTANT STEP
# Monthly restore test script:
#!/bin/bash
set -euo pipefail
LOG="/var/log/restore-test-$(date +%Y%m%d).log"echo "$(date): Starting restore test" | tee -a $LOG# Get latest backup:
LATEST=$(ls -t /backups/appdb_*.sql.gz | head -1)
echo "$(date): Using backup: $LATEST" | tee -a $LOG# Create a test database:
createdb test_restore_$(date +%Y%m%d)# Restore into it:
gunzip -c "$LATEST" | psql test_restore_$(date +%Y%m%d)# Verify row counts match production:
PROD_COUNT=$(psql appdb -t -c "SELECT COUNT(*) FROM users;")
TEST_COUNT=$(psql test_restore_$(date +%Y%m%d) -t -c "SELECT COUNT(*) FROM users;")if [ "$PROD_COUNT" = "$TEST_COUNT" ]; then
echo "$(date): RESTORE TEST PASSED — counts match: $PROD_COUNT" | tee -a $LOG
else
echo "$(date): RESTORE TEST FAILED — prod: $PROD_COUNT test: $TEST_COUNT" | tee -a $LOG
exit 1
fi# Clean up test database:
dropdb test_restore_$(date +%Y%m%d)
echo "$(date): Restore test complete" | tee -a $LOGOFFSITE BACKUP — RCLONE TO S3
rclone syncs files to cloud storage (AWS S3, Google Cloud Storage, Backblaze B2):
# Install:
curl https://rclone.org/install.sh | sudo bash# Configure (interactive setup):
rclone config# Copy backup to S3:
rclone copy /backups/appdb_$(date +%Y%m%d).sql.gz s3:my-backup-bucket/databases/# Sync entire backup directory:
rclone sync /backups/ s3:my-backup-bucket/# Cost estimate for Indian companies:
# AWS S3 ap-south-1 (Mumbai): ~$0.023/GB/month
# 100GB of backups = ~$2.30/month = ~₹190/monthThe morning after her restore test succeeded, Meera sent a message in the company Slack: Restore test passed. 12 minutes to full recovery. The backup system works. Her manager wrote back: this is the first time I have felt genuinely safe about our data in 3 years.
3-2-1 backup rule: 3 copies, 2 different media, 1 offsite — a backup that only lives on the same server is not a backup
Test your restore every month — an untested backup is just a file you hope works
pg_dump -Fc creates a custom format dump that restores faster and supports parallel restore
rsync --delete mirrors a directory exactly and only transfers changed files — much faster than tar for large directories
rclone sync to S3 ap-south-1 Mumbai costs around 190 rupees per month for 100GB — offsite backup is affordable