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 sudo privileges.
  • 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:

FailureWhat saves you
Accidental rm, bad deploy, corrupted fileAny backup with enough history (versioning)
Disk or server failureA copy on a different system
Data center outage or disasterThe offsite copy
Compromised server or ransomwareA 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.

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:

WorkloadRPORTOPractical approach
Brochure website24 h1 dayNightly file and database backup
Online shop, SaaS app1 h or less1 to 4 hHourly database dumps or binlog/WAL archiving, nightly files
Internal tools, dev servers24 h to 1 weekDaysNightly 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:

DataTypical pathsNotes
System configuration/etcSmall, changes rarely, essential for rebuilds
Web content and apps/var/www, /srv, /optInclude uploaded files
User data/home, /rootExclude caches such as ~/.cache
DatabasesDump files, not raw data directoriesCopying /var/lib/mysql while running gives inconsistent files
Scheduled jobs/var/spool/cron/crontabsUser crontabs live here
Package listOutput of apt-mark showmanualLets you reinstall the same software
TLS certificates/etc/letsencryptAlready 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:

ToolBest forEncryptionDeduplicationOffsite targets
rsync with --link-destSimple file-level snapshots to another serverNo (transport only, over SSH)Per file, through hard linksSSH servers
BorgBackupFast, compressed, encrypted backups to an SSH serverYesYes, block levelSSH servers with Borg installed
resticEncrypted backups straight to object storageYesYes, block levelSFTP, 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:

CopyLocationToolScheduleProtection
1Production disk--Live data
2Backup server in another data centerBorgBackup over SSH, key restricted to borg serve --append-onlyNightly at 02:30Encrypted, the server cannot delete history
3S3-compatible bucket with object lockresticNightly at 04:00Encrypted, 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:

FrequencyTest
WeeklyRestore a few random files and compare them with production
MonthlyRestore the latest database dump into a scratch database and query it
QuarterlyRebuild 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.