A backup strategy answers three questions: what you copy, where the copies live, and how fast you can get a working system back. The 3-2-1 rule is the simplest framework that covers all three. This guide explains the rule and its modern 3-2-1-1-0 variant, shows how to set recovery targets, lists what actually needs backing up on a Linux server, and ends with a concrete, testable plan for an Ubuntu 24.04 server using standard tools.
Prerequisites
This is a planning guide, but the examples assume:
- A Linux server running Ubuntu 24.04 LTS (for example a CubePath VPS) with a non-root user with
sudoprivileges. - A second location to store copies: another server in a different data center, or an S3-compatible object storage bucket.
- Basic familiarity with the command line.
The 3-2-1 rule
The rule says you should keep:
- 3 copies of your data: the production data plus two backups.
- 2 different storage types or systems: for example the server's own disk and a separate backup server. Two folders on the same disk count as one.
- 1 copy offsite: in a different data center or provider, so a fire, flood, power incident or account problem at one location does not take every copy with it.
Each part protects against a different failure:
| Failure | What saves you |
|---|---|
Accidental rm, bad deploy, corrupted file | Any backup with enough history (versioning) |
| Disk or server failure | A copy on a different system |
| Data center outage or disaster | The offsite copy |
| Compromised server or ransomware | A copy the compromised server cannot modify or delete |
The last row is why the rule has grown.
The 3-2-1-1-0 variant
Attackers who gain root on a server usually find the backup credentials on that same server and delete the backups before encrypting data. The 3-2-1-1-0 variant adds two requirements:
- 1 immutable or offline copy: a copy that cannot be changed or deleted from the production server. Examples are a Borg repository in append-only mode, an object storage bucket with object lock (WORM), or a pull-based backup server the production host has no credentials for.
- 0 errors: backups are verified and restores are tested, so you know the copies are usable.
NoteA RAID array, a replica database or a VM snapshot on the same storage is not a backup. RAID and replication copy mistakes and deletions instantly, and snapshots on the same storage disappear with it.
Set RPO and RTO first
Two numbers drive every other decision:
- RPO (Recovery Point Objective): how much data you can afford to lose, measured in time. An RPO of 24 hours means a nightly backup is enough; an RPO of 15 minutes means you need continuous database log shipping or very frequent dumps.
- RTO (Recovery Time Objective): how long the service may be down while you restore. Restoring 500 GB from a remote object store over the internet can take hours, which may be longer than your RTO allows.
Typical targets look like this:
| Workload | RPO | RTO | Practical approach |
|---|---|---|---|
| Brochure website | 24 h | 1 day | Nightly file and database backup |
| Online shop, SaaS app | 1 h or less | 1 to 4 h | Hourly database dumps or binlog/WAL archiving, nightly files |
| Internal tools, dev servers | 24 h to 1 week | Days | Nightly or weekly backup, configuration in Git |
Write your numbers down. If the RTO is short, keep one recent copy close to the server (fast restore) and use the offsite copy for disasters.
Decide what to back up
Backing up the whole disk is simple but slow to restore selectively and wastes space on files the package manager can reinstall. On a typical Ubuntu server, focus on:
| Data | Typical paths | Notes |
|---|---|---|
| System configuration | /etc | Small, changes rarely, essential for rebuilds |
| Web content and apps | /var/www, /srv, /opt | Include uploaded files |
| User data | /home, /root | Exclude caches such as ~/.cache |
| Databases | Dump files, not raw data directories | Copying /var/lib/mysql while running gives inconsistent files |
| Scheduled jobs | /var/spool/cron/crontabs | User crontabs live here |
| Package list | Output of apt-mark showmanual | Lets you reinstall the same software |
| TLS certificates | /etc/letsencrypt | Already covered if you back up /etc |
Save the list of manually installed packages so a rebuild installs the same software:
apt-mark showmanual | sudo tee /etc/apt-manual-packages.txt > /dev/null
Because the file lives in /etc, it is included in every backup of that directory.
Databases need a consistent dump
Copying the files of a running database produces a backup that may not start. Use the database's own dump tool, and back up the dump file.
For MySQL or MariaDB with InnoDB tables, --single-transaction produces a consistent snapshot without locking tables:
sudo mysqldump --single-transaction --routines --events --all-databases | gzip > /tmp/mysql-all.sql.gz
For PostgreSQL, the custom format (-Fc) is compressed and allows restoring individual tables:
sudo -u postgres pg_dump -Fc your_database > /tmp/your_database.dump
On Ubuntu 24.04, root authenticates to MySQL and MariaDB through the Unix socket, so sudo mysqldump works without a password.
If your RPO is shorter than your dump interval, look at MySQL binary logs or PostgreSQL WAL archiving for point-in-time recovery.
Choose backup types and tools
There are three classic backup types:
- Full: a complete copy each time. Simple to restore, expensive to store.
- Incremental: only changes since the last backup. Small and fast, but a restore needs the chain.
- Differential: changes since the last full backup.
Modern deduplicating tools make this distinction mostly irrelevant: every snapshot behaves like a full backup when you restore, but only new data blocks are stored and transferred. For Linux servers, these three tools cover almost every need:
| Tool | Best for | Encryption | Deduplication | Offsite targets |
|---|---|---|---|---|
rsync with --link-dest | Simple file-level snapshots to another server | No (transport only, over SSH) | Per file, through hard links | SSH servers |
| BorgBackup | Fast, compressed, encrypted backups to an SSH server | Yes | Yes, block level | SSH servers with Borg installed |
| restic | Encrypted backups straight to object storage | Yes | Yes, block level | SFTP, S3-compatible storage, many others |
Retention: how long to keep copies
Keeping only the last backup is dangerous: if a file was corrupted three days ago, the latest backup contains the corrupted version. A grandfather-father-son (GFS) policy keeps many recent copies and fewer old ones:
- 7 daily backups
- 4 weekly backups
- 6 to 12 monthly backups
Both Borg (borg prune --keep-daily 7 --keep-weekly 4 --keep-monthly 6) and restic (restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --prune) implement this directly. Check your legal obligations too: invoices or logs may need to be kept for years, while personal data may need to be deleted after a defined period.
Example: a 3-2-1-1-0 plan for one server
Here is a concrete plan for a web server with a MySQL database, an RPO of 24 hours and an RTO of 4 hours:
| Copy | Location | Tool | Schedule | Protection |
|---|---|---|---|---|
| 1 | Production disk | - | - | Live data |
| 2 | Backup server in another data center | BorgBackup over SSH, key restricted to borg serve --append-only | Nightly at 02:30 | Encrypted, the server cannot delete history |
| 3 | S3-compatible bucket with object lock | restic | Nightly at 04:00 | Encrypted, immutable for the lock period |
Every night a database dump runs first, then both tools back up /etc, /home, /root, /var/www and the dump directory. Pruning of the append-only Borg repository is done from the backup server itself, never from production.
Copies 2 and 3 use different tools, different providers and different credentials, so a bug in one tool or a leaked key does not affect the other.
Monitor every run
A backup job that fails silently is worse than none, because you believe you are protected. For each job:
- Run it as a systemd service or cron job and check the exit code. Borg and restic return non-zero on errors.
- Send a success signal to a dead man's switch (a monitoring service that alerts you when an expected ping does not arrive), so you notice jobs that never ran at all.
- Review the logs weekly at first:
journalctl -u borg-backup.service --since "7 days ago"
- Watch the free space on the backup target. A full disk is the most common reason backups stop.
Test restores regularly
The "0 errors" part of the rule only holds if you restore on a schedule. A reasonable routine:
| Frequency | Test |
|---|---|
| Weekly | Restore a few random files and compare them with production |
| Monthly | Restore the latest database dump into a scratch database and query it |
| Quarterly | Rebuild the whole server on a fresh VPS from backups only, and time it against your RTO |
A file-level test compares a restored directory with the live one. Restore /etc/nginx from your backup tool into /tmp/restore-test, then run:
sudo diff -r /etc/nginx /tmp/restore-test/etc/nginx && echo "restore matches"
restore matches
A database test loads the dump into a temporary database:
sudo mysql -e "CREATE DATABASE restore_test"
zcat /tmp/restore/your_database.sql.gz | sudo mysql restore_test
sudo mysql -e "SELECT COUNT(*) FROM restore_test.your_table"
sudo mysql -e "DROP DATABASE restore_test"
For this test, dump the single database with mysqldump --single-transaction your_database, because an --all-databases dump contains CREATE DATABASE and USE statements that restore into the original names.
Write the full-rebuild procedure down as a runbook while you do the quarterly test: which server size, which packages, where the backup credentials are stored, and in what order you restore. Keep a copy of the runbook and the backup encryption keys outside the server, for example in a password manager.
Checklist
- RPO and RTO are written down and the backup schedule matches them.
- Three copies, on two systems, with one offsite.
- At least one copy cannot be deleted from the production server.
- Databases are backed up from dumps, not raw data files.
- Encryption keys and passphrases are stored outside the server.
- Every job reports failures, and a missing run triggers an alert.
- Restores are tested on a schedule, and the last test date is recorded.
Conclusion
The 3-2-1 rule gives you redundancy, the extra "1" protects against an attacker with root on your server, and the "0" comes only from verified restores. Start by writing down your RPO and RTO, then implement the copies one at a time. Good next steps are setting up rsync snapshots, an encrypted BorgBackup repository on a second server, or offsite backups to object storage with restic.
