Implement automated backup strategy for VPS servers with regular snapshots, off-server storage, and retention policies to enable quick disaster recovery.
Scanned 5/31/2026
Install via CLI
openskills install diegosouzapw/awesome-omni-skill---
name: backup-strategy
description: Implement automated backup strategy for VPS servers with regular snapshots, off-server storage, and retention policies to enable quick disaster recovery.
license: MIT
compatibility: Ubuntu, Debian, CentOS, RHEL, and most Linux distributions
metadata:
author: secure-server-skill
version: "1.0"
category: disaster-recovery
allowed-tools: Bash(tar:*, gzip:*, rsync:*, cron:*, aws:*, scp:*)
---
# Backup Strategy Skill
Implement automated backup solutions for VPS servers to ensure quick recovery from security incidents or system failures.
## What This Skill Does
This skill helps AI agents configure automated backup systems on VPS servers. Security isn't just prevention - it's recovery. If your server gets compromised, you need to rebuild quickly. Regular, off-server backups are essential for business continuity and disaster recovery.
**Key capabilities:**
- Create automated backup scripts
- Schedule regular backups with cron
- Implement retention policies (keep N days of backups)
- Compress and encrypt backup archives
- Store backups off-server (S3, remote server, etc.)
- Verify backup integrity
- Document restoration procedures
## When to Use
Use this skill when you need to:
- Set up new server with backup strategy
- Implement disaster recovery plan
- Comply with data retention requirements
- Protect against ransomware and data loss
- Enable quick server rebuilds
- Meet business continuity requirements
**Critical understanding:** The backup must NOT be on the same server. If the server is compromised, local backups can be deleted or encrypted by attackers.
## Prerequisites
- Root or sudo access to the server
- Sufficient disk space for temporary backups
- Off-server storage solution (S3, remote server, NAS, etc.)
- Understanding of what needs to be backed up
- Credentials for remote storage (if applicable)
## What to Back Up
### Critical Directories
```bash
/home # User home directories
/etc # System and application configuration
/var/www # Web server content
/var/lib/mysql # MySQL databases (if using file-based)
/root # Root user home (if used)
/opt # Optional software installations
/usr/local # Locally installed software
```
### What NOT to Back Up
```bash
/tmp # Temporary files
/var/tmp # Temporary files
/proc # Virtual filesystem
/sys # Virtual filesystem
/dev # Device files
/run # Runtime data
/var/cache # Cache files
```
## Basic Backup Script
### Simple Tar-Based Backup
Create `/usr/local/bin/backup.sh`:
```bash
#!/bin/bash
#
# Simple backup script using tar and gzip
#
# Configuration
BACKUP_DIR="/backup"
DATE=$(date +%Y-%m-%d)
BACKUP_NAME="backup-$DATE.tar.gz"
RETENTION_DAYS=7
# Create backup directory
mkdir -p "$BACKUP_DIR"
# Create compressed archive
echo "Creating backup: $BACKUP_NAME"
tar -czf "$BACKUP_DIR/$BACKUP_NAME" \
--exclude='/backup' \
--exclude='/proc' \
--exclude='/sys' \
--exclude='/dev' \
--exclude='/run' \
--exclude='/tmp' \
--exclude='/var/tmp' \
--exclude='/var/cache' \
/home \
/etc \
/var/www \
/root \
2>/var/log/backup-error.log
# Check if backup was successful
if [ $? -eq 0 ]; then
echo "Backup completed successfully"
echo "Backup saved to: $BACKUP_DIR/$BACKUP_NAME"
else
echo "Backup failed! Check /var/log/backup-error.log"
exit 1
fi
# Delete old backups (keep last N days)
echo "Cleaning up old backups (keeping last $RETENTION_DAYS days)..."
find "$BACKUP_DIR" -name "backup-*.tar.gz" -mtime +$RETENTION_DAYS -delete
echo "Backup process complete"
```
Make it executable:
```bash
sudo chmod +x /usr/local/bin/backup.sh
```
## Advanced Backup Strategies
### Database Backups
**MySQL/MariaDB:**
```bash
#!/bin/bash
# MySQL backup script
DB_USER="root"
DB_PASS="your_password"
BACKUP_DIR="/backup/mysql"
DATE=$(date +%Y-%m-%d)
mkdir -p "$BACKUP_DIR"
# Backup all databases
mysqldump -u"$DB_USER" -p"$DB_PASS" --all-databases \
--single-transaction \
--quick \
--lock-tables=false \
> "$BACKUP_DIR/all-databases-$DATE.sql"
# Compress
gzip "$BACKUP_DIR/all-databases-$DATE.sql"
# Delete old backups
find "$BACKUP_DIR" -name "all-databases-*.sql.gz" -mtime +7 -delete
```
**PostgreSQL:**
```bash
#!/bin/bash
# PostgreSQL backup script
BACKUP_DIR="/backup/postgresql"
DATE=$(date +%Y-%m-%d)
mkdir -p "$BACKUP_DIR"
# Backup all databases
sudo -u postgres pg_dumpall > "$BACKUP_DIR/pg-backup-$DATE.sql"
# Compress
gzip "$BACKUP_DIR/pg-backup-$DATE.sql"
# Delete old backups
find "$BACKUP_DIR" -name "pg-backup-*.sql.gz" -mtime +7 -delete
```
### Incremental Backups with rsync
```bash
#!/bin/bash
# Incremental backup using rsync
BACKUP_DIR="/backup/incremental"
CURRENT="$BACKUP_DIR/current"
DATE=$(date +%Y-%m-%d-%H%M%S)
SNAPSHOT="$BACKUP_DIR/$DATE"
# Create backup directory
mkdir -p "$BACKUP_DIR"
# Perform incremental backup
rsync -av --delete \
--link-dest="$CURRENT" \
--exclude='/backup' \
--exclude='/proc' \
--exclude='/sys' \
/home \
/etc \
/var/www \
"$SNAPSHOT"
# Update current symlink
rm -f "$CURRENT"
ln -s "$SNAPSHOT" "$CURRENT"
# Keep only last 10 snapshots
ls -1dt "$BACKUP_DIR"/2* | tail -n +11 | xargs rm -rf
```
## Off-Server Storage
### AWS S3 Backup
```bash
#!/bin/bash
# Backup to AWS S3
BACKUP_DIR="/backup"
S3_BUCKET="s3://my-backups/server-name"
DATE=$(date +%Y-%m-%d)
BACKUP_FILE="backup-$DATE.tar.gz"
# Create backup
tar -czf "$BACKUP_DIR/$BACKUP_FILE" /home /etc /var/www
# Upload to S3
aws s3 cp "$BACKUP_DIR/$BACKUP_FILE" "$S3_BUCKET/"
# Verify upload
if [ $? -eq 0 ]; then
echo "Backup uploaded to S3 successfully"
# Remove local copy after successful upload
rm "$BACKUP_DIR/$BACKUP_FILE"
else
echo "S3 upload failed!"
exit 1
fi
# S3 lifecycle policy handles retention
```
### SCP to Remote Server
```bash
#!/bin/bash
# Backup to remote server via SCP
BACKUP_DIR="/backup"
REMOTE_USER="backup"
REMOTE_HOST="backup-server.example.com"
REMOTE_DIR="/backups/webserver"
DATE=$(date +%Y-%m-%d)
BACKUP_FILE="backup-$DATE.tar.gz"
# Create backup
tar -czf "$BACKUP_DIR/$BACKUP_FILE" /home /etc /var/www
# Upload via SCP (requires SSH key authentication)
scp "$BACKUP_DIR/$BACKUP_FILE" "$REMOTE_USER@$REMOTE_HOST:$REMOTE_DIR/"
# Verify upload
if [ $? -eq 0 ]; then
echo "Backup transferred successfully"
rm "$BACKUP_DIR/$BACKUP_FILE"
else
echo "Transfer failed!"
exit 1
fi
```
### Encrypted Backups
```bash
#!/bin/bash
# Create encrypted backup
BACKUP_DIR="/backup"
DATE=$(date +%Y-%m-%d)
BACKUP_FILE="backup-$DATE.tar.gz"
ENCRYPTED_FILE="backup-$DATE.tar.gz.gpg"
GPG_RECIPIENT="admin@example.com"
# Create compressed backup
tar -czf "$BACKUP_DIR/$BACKUP_FILE" /home /etc /var/www
# Encrypt with GPG
gpg --encrypt --recipient "$GPG_RECIPIENT" \
--output "$BACKUP_DIR/$ENCRYPTED_FILE" \
"$BACKUP_DIR/$BACKUP_FILE"
# Remove unencrypted version
rm "$BACKUP_DIR/$BACKUP_FILE"
# Upload encrypted backup (S3, SCP, etc.)
# ...
echo "Encrypted backup created: $ENCRYPTED_FILE"
```
## Scheduling Backups with Cron
### Edit Crontab
```bash
sudo crontab -e
```
### Common Schedules
```bash
# Daily at 2 AM
0 2 * * * /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1
# Weekly on Sunday at 3 AM
0 3 * * 0 /usr/local/bin/backup.sh
# Daily at 2 AM, keep 30 days
0 2 * * * /usr/local/bin/backup.sh && find /backup -name "backup-*.tar.gz" -mtime +30 -delete
# Every 6 hours
0 */6 * * * /usr/local/bin/backup.sh
# Monthly on the 1st at midnight
0 0 1 * * /usr/local/bin/backup.sh
```
### Cron with Logging
```bash
# Daily backup with logging and email on failure
0 2 * * * /usr/local/bin/backup.sh > /var/log/backup-$(date +\%Y\%m\%d).log 2>&1 || mail -s "Backup Failed" admin@example.com < /var/log/backup-$(date +\%Y\%m\%d).log
```
## Backup Verification
### Check Backup Integrity
```bash
#!/bin/bash
# Verify backup archive integrity
BACKUP_FILE="/backup/backup-2024-01-31.tar.gz"
# Test gzip integrity
gzip -t "$BACKUP_FILE"
if [ $? -eq 0 ]; then
echo "Backup archive is valid"
else
echo "Backup archive is corrupted!"
exit 1
fi
# Test tar contents
tar -tzf "$BACKUP_FILE" > /dev/null
if [ $? -eq 0 ]; then
echo "Tar archive structure is valid"
else
echo "Tar archive has errors!"
exit 1
fi
```
### List Backup Contents
```bash
# List files in backup
tar -tzf /backup/backup-2024-01-31.tar.gz | less
# Search for specific file
tar -tzf /backup/backup-2024-01-31.tar.gz | grep "config.php"
```
## Restoration Procedures
### Full System Restore
```bash
#!/bin/bash
# Restore from backup
BACKUP_FILE="/backup/backup-2024-01-31.tar.gz"
# WARNING: This will overwrite existing files!
echo "WARNING: This will restore files and may overwrite existing data!"
read -p "Continue? (yes/no): " CONFIRM
if [ "$CONFIRM" != "yes" ]; then
echo "Aborted"
exit 1
fi
# Extract to root
cd /
tar -xzf "$BACKUP_FILE"
echo "Restore complete. Review extracted files and restart services."
```
### Restore Specific Directory
```bash
# Restore only /etc
tar -xzf /backup/backup-2024-01-31.tar.gz -C / etc/
# Restore specific file
tar -xzf /backup/backup-2024-01-31.tar.gz -C / etc/nginx/nginx.conf
```
### Restore Database
```bash
# MySQL restore
gunzip < /backup/mysql/all-databases-2024-01-31.sql.gz | mysql -uroot -p
# PostgreSQL restore
gunzip < /backup/postgresql/pg-backup-2024-01-31.sql.gz | sudo -u postgres psql
```
## Monitoring and Alerting
### Email Notifications
```bash
#!/bin/bash
# Backup with email notification
BACKUP_SCRIPT="/usr/local/bin/backup.sh"
ADMIN_EMAIL="admin@example.com"
# Run backup
if $BACKUP_SCRIPT; then
echo "Backup completed successfully on $(date)" | \
mail -s "Backup Success - $(hostname)" "$ADMIN_EMAIL"
else
echo "Backup failed on $(date)" | \
mail -s "BACKUP FAILED - $(hostname)" "$ADMIN_EMAIL"
fi
```
### Check Last Backup Age
```bash
#!/bin/bash
# Alert if backup is too old
BACKUP_DIR="/backup"
MAX_AGE_HOURS=26 # Alert if no backup in last 26 hours
LATEST_BACKUP=$(find "$BACKUP_DIR" -name "backup-*.tar.gz" -type f -printf '%T@ %p\n' | sort -n | tail -1 | cut -d' ' -f2-)
if [ -z "$LATEST_BACKUP" ]; then
echo "No backups found!" | mail -s "BACKUP ALERT" admin@example.com
exit 1
fi
AGE_SECONDS=$(($(date +%s) - $(stat -c %Y "$LATEST_BACKUP")))
AGE_HOURS=$((AGE_SECONDS / 3600))
if [ $AGE_HOURS -gt $MAX_AGE_HOURS ]; then
echo "Last backup is $AGE_HOURS hours old!" | \
mail -s "BACKUP TOO OLD" admin@example.com
fi
```
## Security Best Practices
1. **Off-server storage** - Never rely solely on local backups
2. **Encryption** - Encrypt sensitive backups, especially if storing remotely
3. **Access control** - Restrict backup file permissions (600 or 640)
4. **Test restores** - Regularly test that backups can be restored
5. **Monitor backup jobs** - Alert on failures
6. **Retention policy** - Balance storage costs with recovery needs
7. **Version backups** - Keep multiple generations
8. **Document procedures** - Maintain restoration runbooks
9. **Separate credentials** - Don't store backup credentials on the server being backed up
## Common Mistakes to Avoid
- ❌ Only backing up to the same server (single point of failure)
- ❌ Not testing restore procedures
- ❌ Backing up cached/temporary files (waste of space)
- ❌ Not encrypting backups containing sensitive data
- ❌ Setting retention too short (can't recover from old issues)
- ❌ Not monitoring backup success/failure
- ❌ Including backup directory in backup (infinite loop!)
- ❌ Not documenting what's backed up and how to restore
## Additional Resources
See [references/backup-locations.md](references/backup-locations.md) for storage provider comparison.
See [scripts/backup-full.sh](scripts/backup-full.sh) for comprehensive backup script.
See [scripts/backup-mysql.sh](scripts/backup-mysql.sh) for database-specific backup.
## Related Skills
- `auto-updates` - Keep backup tools updated
- `ssh-hardening` - Secure SSH for remote backups
- `firewall-configuration` - Protect backup storage access
No comments yet. Be the first to comment!