Backup & restore strategy for Linux servers: the 3-2-1 principle, tools like rsync, tar, restic, and borgbackup, database and file server backups, to restore procedures and integrity verification done right in production.

After episode 25 where we covered Linux system security hardening — SSH key authentication, firewall, fail2ban, all the way to locking down access with strict users and sudo — your system is now much harder to breach. But there's one reality no admin, no matter how great, can avoid: no matter how well you defend, eventually you must face data loss. An SSD that dies without warning, files deleted by a wrong-target rm -rf, a database corrupted by a power outage in the middle of a transaction, or even a server held hostage by ransomware — none of that is fiction.
The question is no longer whether it will happen, but how fast you can recover. In this episode we cover the last line of defense, and the one most often neglected: backup & restore strategy. We'll break down the 3-2-1 principle, compare tools like rsync, tar, restic, and borgbackup, practice backing up databases and file servers, then close with restore procedures and drills that let you sleep well even when the server misbehaves.
Many beginner admins treat backup as a "when I have time" task. That thinking is dangerous. Consider your server as a cabinet full of diplomas, certificates, and important contracts. You can guard that cabinet with the strongest lock and the best alarm — but if the house burns down, everything is still lost. The sensible solution isn't just locking the cabinet, but keeping copies of important documents in another location.
In the backup world, that solution is formulated into a very famous principle: the 3-2-1 rule.
+-----------------------+ +----------------------+ +------------------+
| Production Data | --> | Primary Backup | --> | Offsite Backup |
| (main server) | | (NAS/ext. disk) | | (cloud/other DC)|
+-----------------------+ +----------------------+ +------------------+
1 copy (active) 2 copies, media #1 3 copies, media #2
= 1 separate locationImportant
The 3-2-1 principle is a minimum, not an ideal target. In the ransomware era, many organizations move to the 3-2-1-1-0 variant: an additional 1 air-gapped copy (physically disconnected from the network) and 0 errors on restore (backups must be proven restorable). Notorious ransomware waits for years until it finds a restorable backup, then encrypts everything including the backup. An air-gapped copy leaves the attacker with no valuable "ransom".
Now you understand why we back up. Next, how to do it — and tool choice greatly determines the quality of your backups.
Linux gives you a wide spectrum of tools, from simple to advanced. The key to choosing isn't "which tool is the coolest", but which tool fits your recovery needs. Every tool has trade-offs between simplicity, speed, storage space, and ease of restore.
rsync was already covered in the file sharing episode, and in the backup world it's the main workhorse. Its strength lies in incremental transfer: rsync only sends changed data, so subsequent backups are very fast and bandwidth-efficient. It also supports hardlink-based deduplication when combined with a timestamped directory structure.
rsync -avz --delete \
/var/www/ \
backup@nas.local:/backups/www/latest/The pattern above is a baseline. To get daily snapshots without eating extra space, many admins use the rsync --link-dest trick: every day we back up into a dated directory, but unchanged files are just hardlinks to yesterday's backup. The result: you have 30 daily versions that effectively take only the size of one full backup.
TODAY=$(date +%F)
rsync -a --delete --link-dest=../latest \
/var/www/ backup@nas.local:/backups/www/$TODAY/Tip
Why is the latest + date pattern so popular? Because restore is instant: you can pick a file version exactly as it was on a specific date, not just the newest version. Files that never changed since day one only use one disk block — very efficient storage. Make sure the first (full) backup is placed as latest so the --link-dest reference is valid from the start.
tar is the oldest archiving tool still alive today. Its strength: one self-contained and easily movable archive file — great for config snapshots, project archives, or server migrations. Its weakness: every full backup re-reads all data, so it's less efficient for data that changes fast in large volumes.
tar -czf /backups/etc-$(date +%F).tar.gz /etcFor rarely changing files like /etc, tar is a very reasonable choice. The combination of tar for configs and rsync for dynamic data is a healthy, common pattern in many organizations.
For more serious scale, modern tools like restic and borgbackup offer features that used to exist only in paid enterprise solutions: data deduplication (identical data blocks are stored only once, even across different files), end-to-end encryption, and time-based snapshots.
# 1. Initialize repository + password
restic init --repo /backups/restic-repo
# 2. Back up important directories
restic -r /backups/restic-repo backup /etc /home /var/www
# 3. List snapshots
restic -r /backups/restic-repo snapshotsNote the first line: restic immediately asks for a password to encrypt the repository. All data entering the repo is already encrypted before it leaves the server — so sending it to the cloud is not a big deal, because its contents can't be read by anyone without the password. This is why restic/borg are the top choice for offsite backups.
Note
Deduplication works by splitting files into chunks and storing each unique chunk once. Imagine 100 VMs running the same OS: most of their OS blocks are identical. With deduplication, backing up those 100 VMs is only slightly bigger than backing up one VM — space savings of up to 90% for homogeneous workloads.
A manually run backup is a backup that one day won't be run. Automation via cron or a systemd timer is a must, not a luxury. The most common pattern:
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin
# Backup database & file server every day at 02:30
30 2 * * * root /usr/local/sbin/backup-server.sh >> /var/log/backup.log 2>&1Warning
cron only runs the script — it doesn't know whether the backup succeeded. Never treat "the log isn't empty" as a sign of success. Implement exit codes and alerting: have the script return a non-zero status on failure, then monitor via email, Slack, or a monitoring stack (which we'll build in episode 29). A backup silently failing for weeks is the most common nightmare scenario in the real world.
One non-negotiable rule: backups must be periodically restore-tested. A backup never tested for restore is worth the same as having no backup at all — maybe even more dangerous, because it gives a false sense of security. Test restore at least once a month, and record the results.
Backing up files like /etc and /home is relatively simple — just copy them. Databases are a different story: copying raw data files (e.g. *.frm, *.ibd, *.pgdata) while the database is running almost certainly yields a corrupted backup. Databases write to disk in buffers and inconsistent transactions; copying in the middle of that is like photographing a book someone is copying while the pages keep turning.
The solution is using the database's built-in dump tool, which produces a consistent logical snapshot:
mysqldump --single-transaction --quick --routines \
-u backup -p myapp_db | gzip > /backups/db/myapp-$(date +%F).sql.gzpg_dump -U backup -Fc myapp_db > /backups/db/myapp-$(date +%F).dumpA few things to note:
--single-transaction on mysqldump produces a consistent snapshot without locking tables for a long time.-Fc (custom) format on pg_dump produces a compressed file that can be restored selectively per table.Tip
For large database backups (tens to hundreds of GB), consider weekly full backups + daily WAL/binary log archiving. In PostgreSQL, this means a base backup plus write-ahead logs (WAL) enabling point-in-time recovery: you can restore the database to the state seconds before the disaster happened, not just to the last backup point.
Restore is the side of backup that's never popular until needed — and that's when all weaknesses surface. A good procedure always runs in a clear order that anyone can practice, including someone newly joined:
For tar archives, verification can be done directly with this command:
tar -tvzf /backups/etc-2026-08-01.tar.gz | head -20For databases, the strongest test is restoring into an empty environment then comparing row counts:
createdb myapp_restore
pg_restore -d myapp_restore /backups/db/myapp-2026-08-01.dump
psql -d myapp_restore -c "SELECT count(*) FROM orders;"mysql -u root -p myapp_db < /backups/db/myapp-2026-08-01.sql.gzCaution
Beware the order of restore. Full backups should usually be restored before incremental/log archive backups. Mixing the order — for example applying WALs from an older period after a newer base backup — produces an inconsistent database. Always write the restore order in the runbook and follow it literally during drills.
Time to combine everything into a single script that can be scheduled. The following script backs up critical files and the database, adds a restic snapshot, then cleans up old snapshots with a retention policy:
#!/usr/bin/env bash
set -euo pipefail
REPO="/backups/restic-repo"
export RESTIC_PASSWORD_FILE="/etc/restic/passphrase"
log() { echo "[$(date +%F_%T)] $*"; }
log "Backing up the database..."
mysqldump --single-transaction -u backup myapp_db \
| gzip > /var/tmp/myapp-$(date +%F).sql.gz
log "Backing up the file server with restic..."
restic -r "$REPO" backup /etc /home /var/www /var/tmp/myapp-*.sql.gz
log "Cleaning up old snapshots (retention 7/4/6)..."
restic -r "$REPO" forget --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --prune
log "Done."Explanation of the important lines:
set -euo pipefail makes the script stop and return a failure status if any single step errors — this is the basis of the alerting discussed above./etc/restic/passphrase file with 600 permissions, not inside the script. Your version of the script should pull from a secret manager, not be committed to Git.forget with --keep-daily/--keep-weekly/--keep-monthly is a common retention policy: keep 7 daily snapshots, 4 weekly, and 6 monthly, then delete the rest with --prune.After the script runs, do a restore drill. A good drill simulates a real disaster: restoring a deleted /etc file, and restoring the database into an empty environment:
SNAP=$(restic -r /backups/restic-repo snapshots --latest -q)
restic -r /backups/restic-repo restore "$SNAP" \
--target /mnt/restore --include /etc
ls -la /mnt/restore/etc/ssh/SNAP=$(restic -r /backups/restic-repo snapshots --latest -q)
mkdir -p /mnt/restore && \
restic -r /backups/restic-repo restore "$SNAP" --target /mnt/restore
gzip -dc /mnt/restore/var/tmp/myapp-*.sql.gz | mysql -u root myapp_dbImportant
Measured time is the success metric of a backup. Record how long a restore drill takes from start until the app is back up (known as the Recovery Time Objective/RTO). If your RTO is 4 hours but the restore actually takes 2 days, your backup is operationally failing — and you only find out at the worst moment. Measure now, not at the time of a disaster.
After decades of backup practice, the same failure patterns keep recurring. Recognize and avoid them:
| Pitfall | Why It's Dangerous | Solution |
|---|---|---|
| Backups never restore-tested | A corrupted backup is only discovered when needed | Schedule monthly drills; measure RTO |
| Encrypted backup without key management | Lost password = entire backup useless | Store passphrases in a secret manager + back up keys separately offsite |
rsync --delete against the wrong target | Deletes valid files on the backup side just because the source is broken | Test with --dry-run, avoid --delete on multi-purpose targets |
| One medium for all copies | A single failure destroys all copies | Apply 3-2-1: different media + offsite |
| Database backup without a logical snapshot | Raw files copied while the DB is active are almost certainly corrupted | Use mysqldump/pg_dump or a filesystem snapshot |
| No defined retention | Disk full, or old unneeded data kept forever | Set a retention policy and run forget --prune |
The rsync --delete case deserves special attention. If you run rsync -av --delete /source /destination and the source turns out empty or badly mounted, rsync dutifully deletes every file on the destination. This is one of the fastest ways to destroy a backup. Guard against it with --dry-run first, or use a tool with a snapshot model like restic, which never deletes old data without an explicit policy:
rsync -av --delete /var/www/ /backups/www/
rsync -av --dry-run /var/www/ /backups/www/
# Always review the dry-run output before running the real versionWarning
One question you should always ask whenever writing a backup script: "could this script delete data unintentionally?" If the answer is yes — like --delete, rm -rf, or overwriting — add protection: --dry-run mode, backups into dated directories, or a condition check before execution. One wrong click is enough to wipe six months of backups.
In this episode we built a server's last line of defense: the backup & restore strategy. We understood the 3-2-1 principle as the foundation, chose tools based on restore needs — rsync for incremental synchronization, tar for static archives, and restic/borgbackup for deduplication and encryption. We also covered how to back up databases correctly via mysqldump/pg_dump, automation with cron, ordered restore procedures, and measured drills. Most importantly, we learned that a backup is not a product — it's a process that must be tested, measured, and maintained continuously.
With a solid backup, you're ready to face the worst-case scenario. But backup is only half the story; the other half is how services are run and scaled. In the next episode 27, we'll cover containerization & virtualization — Docker for application containers, LXD for system containers, and KVM for full virtual machines. You'll see how the concepts we've learned from episode 0 to 26 converge into the modern way of running services: lightweight, isolated, and easily movable. See you in episode 27!