| name | linux-backup-restore |
| description | Use when user wants to back up a Linux server, design a backup strategy, write a backup script, set up automated backups, configure restic or borgbackup, restore from a backup, test backup integrity, back up databases, Docker volumes, or asks about the 3-2-1 backup rule or disaster recovery. |
| version | 1.7.0 |
| author | Lehnert |
Linux Backup & Restore
Overview
Designs and generates a complete backup solution tailored to what the user needs to protect. Presents tool options, handles the full lifecycle: backup creation, encryption, remote transfer, retention, integrity testing, and restore procedures. Writes all scripts and configs to disk.
Language: Respond in the user's language. All scripts and configs in English.
When to Use
- User wants to back up a server, directory, database, or Docker volumes
- User asks "how do I back up my server?"
- User wants automated backups on a schedule
- User needs to restore from a backup
- User wants to verify backup integrity
- User wants off-site or cloud backups (S3, Backblaze B2, SSH remote)
- User asks about backup strategies, retention policies, or disaster recovery
When NOT to Use
- User wants to sync files in real time → use rsync in script via /linux-shell-scriptor
- User wants to monitor backup jobs → use /linux-monitoring-setup
- User wants to automate only the scheduling → use /linux-cron-manager
Step 1 — Understand the Backup Need
Map the user's request to a mode. If unclear, ask ONE question.
| User says | Mode |
|---|
| "back up my server", "back up my VPS" | Full server backup strategy |
| "back up my database", "PostgreSQL backup", "MySQL backup" | Database backup |
| "back up Docker volumes" | Docker volume backup |
| "back up my files / directory" | Directory backup |
| "set up automated backups" | Scheduled backup with cron/systemd |
| "restore from backup", "I lost my data" | Restore procedure |
| "test my backups", "verify integrity" | Backup testing |
| "off-site backup", "remote backup", "S3", "cloud" | Remote / cloud backup |
If the scope is vague, ask:
"What do you want to back up? (e.g. specific directories, databases, Docker volumes, or the whole server)"
Step 2 — Present Tool Options
When the user hasn't specified a tool, present options based on the use case:
For general file/directory backups:
Here are the best backup tools for your use case. Which fits your needs?
1. Restic (Recommended)
Modern, fast, encrypted-by-default backup with deduplication. Supports local, SSH, S3, Backblaze B2, and many other backends. Easy restore and snapshot management.
Best for: most use cases, especially with remote/cloud storage.
2. BorgBackup
Excellent deduplication and compression. Encrypted. Slightly faster than restic for large datasets on the same machine. Requires borg on both ends for remote.
Best for: large datasets, same-machine or SSH remote backup.
3. rsync
Simple, fast, widely available. No deduplication or encryption built-in. Best for simple directory mirroring.
Best for: simple local or SSH mirror, no versioning needed.
4. tar + gpg
Basic but universal. Compressed archive with optional GPG encryption. No incremental, no deduplication.
Best for: one-off archives, maximum compatibility.
For database backups:
Present: pg_dump (PostgreSQL), mysqldump / xtrabackup (MySQL/MariaDB), mongodump (MongoDB), plus restic wrapping the dump file.
For Docker volumes:
Present: docker run --volumes-from + tar, or restic with Docker volume path, or pre-stop dump approach.
Backup Tool Specifications
Restic
Initialize repository:
restic init --repo /mnt/backups/myserver
export AWS_ACCESS_KEY_ID=your_key
export AWS_SECRET_ACCESS_KEY=your_secret
restic init --repo s3:https://s3.amazonaws.com/bucket-name/myserver
restic init --repo sftp:user@backuphost:/backups/myserver
export AWS_ACCESS_KEY_ID=your_key
export AWS_SECRET_ACCESS_KEY=your_secret
restic init --repo s3:https://s3.us-east-1.wasabisys.com/bucket-name/myserver
restic init --repo s3:http://minio.example.com:9000/bucket-name/myserver
restic init --repo s3:https://us-east-1.linodeobjects.com/bucket-name/myserver
export B2_ACCOUNT_ID=your_id
export B2_ACCOUNT_KEY=your_key
restic init --repo b2:bucket-name:myserver
Backup script (backup-restic.sh):
#!/usr/bin/env bash
set -euo pipefail
BACKUP_REPO="${BACKUP_REPO:-/mnt/backups/myserver}"
BACKUP_PASSWORD="${RESTIC_PASSWORD:?RESTIC_PASSWORD not set}"
SOURCES=("/etc" "/home" "/var/www" "/opt")
EXCLUDES=("*.log" "*.tmp" "/proc" "/sys" "/dev" "/run" "/tmp")
RETENTION_DAILY=7
RETENTION_WEEKLY=4
RETENTION_MONTHLY=6
LOG_FILE="/var/log/restic-backup.log"
export RESTIC_REPOSITORY="$BACKUP_REPO"
export RESTIC_PASSWORD="$BACKUP_PASSWORD"
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_FILE"; }
EXCLUDE_ARGS=()
for excl in "${EXCLUDES[@]}"; do
EXCLUDE_ARGS+=(--exclude "$excl")
done
log "Starting backup to $BACKUP_REPO"
restic backup "${SOURCES[@]}" "${EXCLUDE_ARGS[@]}" \
--tag "$(hostname)" \
--tag "auto" \
2>&1 | tee -a "$LOG_FILE"
log "Applying retention policy"
restic forget \
--keep-daily "$RETENTION_DAILY" \
--keep-weekly "$RETENTION_WEEKLY" \
--keep-monthly "$RETENTION_MONTHLY" \
--prune \
2>&1 | tee -a "$LOG_FILE"
log "Verifying latest snapshot"
restic check 2>&1 | tee -a "$LOG_FILE"
log "Backup complete"
Restore commands:
restic snapshots
restic restore latest --target /
restic restore abc1234 --target /tmp/restore
restic restore latest --target /tmp/restore --path /etc/nginx
restic mount /mnt/restic-mount
ls /mnt/restic-mount/snapshots/latest/
BorgBackup
Initialize repository:
borg init --encryption=repokey /mnt/backups/myserver
borg init --encryption=repokey user@backuphost:/backups/myserver
Backup script (backup-borg.sh):
#!/usr/bin/env bash
set -euo pipefail
BORG_REPO="${BORG_REPO:-/mnt/backups/myserver}"
export BORG_PASSPHRASE="${BORG_PASSPHRASE:?BORG_PASSPHRASE not set}"
SOURCES="/etc /home /var/www /opt"
LOG_FILE="/var/log/borg-backup.log"
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_FILE"; }
log "Starting borg backup"
borg create \
--verbose \
--filter AME \
--list \
--stats \
--show-rc \
--compression lz4 \
--exclude-caches \
--exclude '/home/*/.cache/*' \
--exclude '/var/tmp/*' \
"${BORG_REPO}::{hostname}-{now:%Y-%m-%dT%H:%M:%S}" \
$SOURCES \
2>&1 | tee -a "$LOG_FILE"
log "Pruning old backups"
borg prune \
--list \
--keep-daily 7 \
--keep-weekly 4 \
--keep-monthly 6 \
"${BORG_REPO}" \
2>&1 | tee -a "$LOG_FILE"
log "Checking repository integrity"
borg check "${BORG_REPO}" 2>&1 | tee -a "$LOG_FILE"
log "Backup complete"
Database Backups
PostgreSQL:
#!/usr/bin/env bash
set -euo pipefail
DB_NAME="${POSTGRES_DB:?}"
DB_USER="${POSTGRES_USER:?}"
BACKUP_DIR="/var/backups/postgresql"
DATE=$(date +%Y%m%d_%H%M%S)
LOG_FILE="/var/log/pg-backup.log"
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_FILE"; }
mkdir -p "$BACKUP_DIR"
log "Dumping $DB_NAME"
pg_dump -U "$DB_USER" -Fc "$DB_NAME" > "${BACKUP_DIR}/${DB_NAME}_${DATE}.dump"
pg_restore --list "${BACKUP_DIR}/${DB_NAME}_${DATE}.dump" > /dev/null \
|| { log "ERROR: Dump is corrupted or unreadable"; exit 1; }
DUMP_KB=$(du -k "${BACKUP_DIR}/${DB_NAME}_${DATE}.dump" | cut -f1)
if [[ $DUMP_KB -lt 10 ]]; then
log "ERROR: Dump suspiciously small (${DUMP_KB}KB) — possible empty database or write failure"
exit 1
fi
log "Dump verified OK (${DUMP_KB}KB)"
find "$BACKUP_DIR" -name "${DB_NAME}_*.dump" -mtime +14 -delete
log "PostgreSQL backup complete: ${DB_NAME}_${DATE}.dump"
Restore PostgreSQL:
dropdb -U "$DB_USER" "$DB_NAME"
createdb -U "$DB_USER" "$DB_NAME"
pg_restore -U "$DB_USER" -d "$DB_NAME" backup.dump
MySQL / MariaDB:
mysqldump \
--single-transaction \
--routines \
--triggers \
--all-databases \
-u root -p"${MYSQL_ROOT_PASSWORD}" \
| gzip > "/var/backups/mysql/all_${DATE}.sql.gz"
gunzip < backup.sql.gz | mysql -u root -p"${MYSQL_ROOT_PASSWORD}"
MongoDB:
#!/usr/bin/env bash
set -euo pipefail
BACKUP_DIR="/var/backups/mongodb"
DATE=$(date +%Y%m%d_%H%M%S)
LOG_FILE="/var/log/mongo-backup.log"
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_FILE"; }
mkdir -p "$BACKUP_DIR"
log "Dumping all MongoDB databases"
mongodump \
--host "${MONGO_HOST:-localhost}" \
--port "${MONGO_PORT:-27017}" \
--username "${MONGO_USER:?}" \
--password "${MONGO_PASSWORD:?}" \
--authenticationDatabase admin \
--out "${BACKUP_DIR}/dump_${DATE}"
log "Compressing dump"
tar czf "${BACKUP_DIR}/mongo_${DATE}.tar.gz" -C "${BACKUP_DIR}" "dump_${DATE}"
rm -rf "${BACKUP_DIR}/dump_${DATE}"
find "$BACKUP_DIR" -name "mongo_*.tar.gz" -mtime +14 -delete
log "MongoDB backup complete: mongo_${DATE}.tar.gz"
tar xzf mongo_YYYYMMDD.tar.gz
mongorestore \
--host "${MONGO_HOST:-localhost}" \
--username "${MONGO_USER}" \
--password "${MONGO_PASSWORD}" \
--authenticationDatabase admin \
--drop \
dump_YYYYMMDD/
Docker Volume Backup
#!/usr/bin/env bash
set -euo pipefail
BACKUP_DIR="/var/backups/docker-volumes"
DATE=$(date +%Y%m%d_%H%M%S)
LOG_FILE="/var/log/docker-volume-backup.log"
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_FILE"; }
mkdir -p "$BACKUP_DIR"
VOLUMES=$(docker volume ls --format '{{.Name}}')
for VOL in $VOLUMES; do
log "Backing up volume: $VOL"
docker run --rm \
-v "${VOL}:/data:ro" \
-v "${BACKUP_DIR}:/backup" \
alpine \
tar czf "/backup/${VOL}_${DATE}.tar.gz" -C /data . \
&& log "✓ $VOL backed up" \
|| log "✗ $VOL FAILED"
done
find "$BACKUP_DIR" -name "*.tar.gz" -mtime +7 -delete
log "Docker volume backup complete"
Restore a Docker volume:
docker compose down
docker run --rm \
-v "${VOLUME_NAME}:/data" \
-v "$(pwd):/backup:ro" \
alpine \
sh -c "cd /data && tar xzf /backup/${VOLUME_NAME}_YYYYMMDD.tar.gz"
docker compose up -d
Backup Testing (Mandatory)
Always include a test-restore script alongside every backup setup:
#!/usr/bin/env bash
set -euo pipefail
RESTORE_DIR="/tmp/backup-test-$(date +%Y%m%d)"
LOG_FILE="/var/log/backup-test.log"
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_FILE"; }
log "=== BACKUP RESTORE TEST ==="
mkdir -p "$RESTORE_DIR"
log "Testing restic restore..."
restic restore latest --target "$RESTORE_DIR" --path /etc/passwd
if [[ -f "${RESTORE_DIR}/etc/passwd" ]]; then
log "✓ Restic restore: PASSED"
else
log "✗ Restic restore: FAILED — /etc/passwd not found in restore"
exit 1
fi
LINES=$(wc -l < "${RESTORE_DIR}/etc/passwd")
if [[ "$LINES" -gt 5 ]]; then
log "✓ File content check: PASSED ($LINES lines in passwd)"
else
log "✗ File content check: FAILED (only $LINES lines)"
exit 1
fi
rm -rf "$RESTORE_DIR"
log "=== TEST COMPLETE: All checks passed ==="
Encryption Key Management
Always include guidance on storing encryption credentials safely. A lost password = permanently unrecoverable backup.
| Tool | Key variable | Storage guidance |
|---|
| restic | RESTIC_PASSWORD | Store in /root/.restic-env (chmod 400) or a password manager; print and store offline |
| BorgBackup | BORG_PASSPHRASE | Same — store in /root/.borg-env (chmod 400); write the passphrase down and store in a safe |
/root/.restic-env pattern (source this before running backup commands):
export RESTIC_REPOSITORY="/mnt/backups/myserver"
export RESTIC_PASSWORD="your-strong-passphrase-here"
chmod 400 /root/.restic-env
Warn the user explicitly: If you lose RESTIC_PASSWORD or BORG_PASSPHRASE, all backups are permanently inaccessible — even if the files exist. Test the password in a restore drill before relying on it.
3-2-1 Backup Rule
Always explain and enforce this when generating a backup strategy:
| Rule | Meaning | Implementation |
|---|
| 3 copies of data | Original + 2 backups | Local backup + remote backup |
| 2 different storage media | Not just 2 copies on same disk | Local disk + cloud or remote SSH |
| 1 off-site | At least one backup physically separate | S3 / Backblaze B2 / remote VPS |
Output Format
Write all scripts to ./backup/ in the current working directory:
backup/
backup.sh ← main backup script
test-restore.sh ← restore test script (auto-generated — run monthly to verify backups work)
.env.example ← required env vars (repo path, password, etc.)
README.md ← what each script does, restore steps
Then print ONLY:
✅ Backup solution created in ./backup/
▶ First-time setup:
cp .env.example .env && nano .env
source .env
# Initialize the backup repository (run once)
[init command for chosen tool]
▶ Run first backup:
chmod +x backup/backup.sh
sudo ./backup/backup.sh
▶ Automate (choose one):
# Cron — daily at 2 AM
echo "0 2 * * * root /path/to/backup/backup.sh" >> /etc/cron.d/backup
# systemd timer — see /linux-cron-manager for a full timer unit
▶ Test your backup (run monthly!):
chmod +x backup/test-restore.sh
./backup/test-restore.sh
💡 Follow the 3-2-1 rule: local + remote/cloud copy + test regularly.
⚠️ Store your encryption password in a password manager AND write it down offline.
If lost, ALL backups are permanently unrecoverable — even if the files exist.
Run the test-restore.sh NOW before trusting these backups with real data.
💡 Next: /linux-cron-manager to create a systemd timer for automated backups.
💡 Next: /linux-monitoring-setup to alert when backups fail or disk fills up.