Why a backup strategy matters more than the backup itself

Packing a tar archive or running a mysqldump is something anyone can do. What actually separates teams is three questions: can you restore within half an hour when something breaks, is the restored data complete, and does your backup survive if the whole server goes down? In ransomware attacks and accidental-deletion incidents hitting small sites, a surprising share of owners discover their "backup" lived only on the same disk as the production server — when the box died, the backup died with it. That is exactly why the industry treats the 3-2-1 rule as a floor, not a ceiling.

The 3-2-1 backup rule

  • 3: Keep 3 copies of backups
  • 2: Store on 2 different media (local disk + remote object storage)
  • 1: Keep at least 1 off-site copy (ideally across zones or clouds)

The advanced variant is 3-2-1-1-0: the extra "1" is an offline or immutable copy to survive ransomware that encrypts your online backups, and the "0" means zero failed restore drills.

Choosing a backup type

Type How it works Storage used Restore speed Best for
Full backup Copies everything each run Large Fast Databases, small/medium sites
Incremental backup Copies only changes since last backup Small Slow (must replay in order) Sites with huge files
Differential backup Copies all changes since last full backup Medium Medium A middle ground

For most personal sites and small business setups, a daily full backup with 30-day retention is the most headache-free combination. Databases are usually a few MB to a few hundred MB; a full dump plus gzip costs almost nothing in complexity, so incremental logic is rarely worth it.

Automated backup script

The script below covers five stages — files, database, cleanup, off-site sync, and verification — and can run from cron every day:

#!/bin/bash
# backup.sh - automated file and database backup
set -euo pipefail

# === Config ===
SITE_DIR="/var/www/example.com"
DB_NAME="wordpress"
DB_USER="root"
DB_PASS="your_password"
BACKUP_DIR="/var/backups"
RETENTION_DAYS=30
DATE=$(date +%Y%m%d_%H%M%S)

# Remote storage config (optional)
RCLONE_REMOTE="s3:bucket-name/backups"

# === 1. Create backup directories ===
mkdir -p "$BACKUP_DIR/files" "$BACKUP_DIR/database"

# === 2. Backup files (excluding cache directories) ===
echo ">>> Backing up files..."
tar -czf "$BACKUP_DIR/files/files_$DATE.tar.gz" \
  --exclude="wp-content/cache" \
  --exclude="wp-content/uploads/backupwp" \
  --exclude="node_modules" \
  --exclude=".git" \
  -C "$SITE_DIR" .

# === 3. Backup the database ===
echo ">>> Backing up database..."
mysqldump -u "$DB_USER" -p"$DB_PASS" "$DB_NAME" \
  --single-transaction --quick --lock-tables=false \
  | gzip > "$BACKUP_DIR/database/db_$DATE.sql.gz"

# === 4. Delete local backups older than the retention period ===
echo ">>> Cleaning old backups..."
find "$BACKUP_DIR/files" -name "*.tar.gz" -mtime +$RETENTION_DAYS -delete
find "$BACKUP_DIR/database" -name "*.sql.gz" -mtime +$RETENTION_DAYS -delete

# === 5. Sync to remote storage (optional) ===
if command -v rclone &>/dev/null; then
    echo ">>> Syncing to remote storage..."
    rclone sync "$BACKUP_DIR" "$RCLONE_REMOTE" --progress
fi

# === 6. Verify backup integrity ===
echo ">>> Verifying backup integrity..."
tar -tzf "$BACKUP_DIR/files/files_$DATE.tar.gz" > /dev/null && echo "Files backup OK"
zcat "$BACKUP_DIR/database/db_$DATE.sql.gz" | head -1 > /dev/null && echo "Database backup OK"

echo "=== Backup complete: $DATE ==="

A few details worth noting:

  • --single-transaction gives a consistent snapshot under InnoDB, and --lock-tables=false avoids locking tables for live traffic — fine on MySQL 5.6+;
  • Using find -mtime +30 for cleanup instead of maintaining a file list means restarts or interruptions never cause stale backups to be missed;
  • set -euo pipefail aborts on any failure, so a silently broken backup script can't slip under the radar.

Reference: mysqldump docs https://dev.mysql.com/doc/refman/8.0/en/mysqldump.html; rclone object storage sync https://rclone.org/s3/

Crontab entry

# Run the backup at 3:00 a.m. daily, appending logs
0 3 * * * /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1

If your database is large, split the job: files at 3:00 and the database at 3:30, so disk I/O doesn't saturate and slow down live requests.

Restore steps

The payoff happens at restore time. Files:

tar -xzf /var/backups/files/files_20260713_030001.tar.gz -C /var/www/example.com/

Database:

gunzip < /var/backups/database/db_20260713_030001.sql.gz | mysql -u root -p"$PASSWORD" db_name

Verify right away: the front page loads, you can log in, and the newest row is there. A backup that has never been restored is not a backup — it is just files taking up disk space.

Consider a real case: a small team running a cross-border e-commerce site wakes up one morning to find crypto-mining code injected across the site and the database encrypted by ransomware. Because their backup script syncs the daily dump to another cloud provider's object storage, the site is back to its previous-day state within 40 minutes, losing at most a handful of order records from the last 24 hours. Meanwhile a team whose only copy lives on the same box can only stare at encrypted files and an encrypted backup. This is exactly when an off-site copy proves its worth.

Run a monthly restore drill

Put a monthly reminder on your calendar and work through three steps:

  1. Restore the latest backup on a clean temporary server;
  2. Check file permissions, database connectivity, and cache directories;
  3. Record how long recovery took end to end, aiming for under 30 minutes.

Suggested backup policy

Data type Frequency Retention Method
Website files Daily 30 days Full + optional incremental
Database Daily 30 days mysqldump + gzip
Config files On every change 90 days Git repo (with history)
Log files Weekly 180 days logrotate
Full snapshot Monthly 12 months Provider snapshot (DR)

Keep config and database separate: put configs in a Git repo so any change in the last 90 days can be reverted, and rely on snapshots for disaster recovery of the database.

Security notes and common mistakes

  1. Backups contain plaintext database data and possibly keys — set file permissions to 600 and directories to 700;
  2. Encrypt remote storage: use the provider's built-in encryption, or GPG-encrypt before uploading;
  3. Store scripts and keys separately — a leaked key is as good as a public backup;
  4. Three recurring mistakes: backing up files but not the database, keeping the backup on the same server, and never testing a restore. These account for most "the data is gone" stories.

16IDC Takeaway

For an individual site owner, a routine of "daily automated backup + snapshot sync to object storage + a monthly restore drill" costs almost nothing yet covers nine out of ten data-loss scenarios. What actually separates you from disaster is not how fancy your backup tooling is, but when your last successful restore test was.