Embercode
✕
Safety and PrivacyLesson 5 of 6

40 min read

Backups and the 3-2-1 rule

Why this matters

Files get lost in ordinary ways. A disk stops working. A laptop is stolen from a bag. A script you wrote saves over the wrong folder. A program called ransomware locks every file it can reach and asks for money to unlock them. In each case the only way back is a copy that was made before the loss.

A backup is a second copy of your data, made at a point in time and kept apart from the original, that you can put back in place when you need it. The last part of that sentence is the part people skip. A copy that cannot be read back is not a backup. It is a hope.

This lesson covers the 3-2-1 rule, a simple plan for how many copies to keep and where to keep them. It also covers the restore test, which is the only way to learn whether a copy works. The examples use the terminal, the text-based way of giving commands to a computer, and a short Python script. Python is usually installed already and needs no extra packages here.

This lesson is about files. The passwords and secrets lessons earlier in this module cover how to protect the keys to your accounts, which a backup does not replace.

The idea: a copy you have not tested is a guess

Start with a story. Asha is writing a thesis in a folder named thesis on her laptop. On Monday she copies the folder to an external drive, a separate disk that connects to the laptop with a cable. On Thursday she deletes a chapter by mistake and saves the file. On Friday the laptop's internal disk stops working.

She connects the external drive to a new laptop and copies Monday's folder back. She loses four days of edits, but the thesis exists. If she had never made the copy, the loss would be the whole thesis.

Now change one detail. The external drive spent a year in a drawer, and Asha never checked it. On Friday she connects it and finds that the thesis folder opens, but two chapter files are damaged and will not open. The copy existed, but it was not usable. The only way to know is to read the copy back, which is called a restore test.

Two terms appear throughout this lesson:

  • Restore means copying a backup back into place and checking that the files are complete and correct.
  • Medium means the kind of storage a copy sits on. Examples are the internal disk of a laptop, a USB drive, a memory card, a tape, or the servers of a cloud storage service.

The 3-2-1 rule

The 3-2-1 rule is a counting plan. It gives three numbers.

  • Three copies of every file that matters. The working copy you edit every day counts as one.
  • Two media types. At least two of the copies sit on different kinds of storage.
  • One copy off site. At least one copy sits in a different physical place, such as another building or a cloud service whose servers are in another location.

Each number protects against a different kind of loss.

Three copies protect against one copy being wrong. If a backup is damaged, you still have two more. A single backup is a single point of failure, which means one thing that, if it breaks, leaves you with nothing.

Two media types protect against a fault that hits one kind of device. Two copies on the same disk do not protect you from that disk failing. Two copies on two drives of the same model, bought together, can share a weakness, so a second kind of storage is safer again.

One copy off site protects against events that destroy everything in one place. A fire, a flood, a burglary or a power surge can take every device in a house. A copy in another location survives those events.

Here is a plan that meets the rule for a student with one laptop:

CopyWhere it livesKind of storageOff site
1Laptop, the working thesis folderInternal solid-state driveNo
2External drive kept at homeUSB hard driveNo
3Cloud storage accountServers run by a cloud providerYes

A solid-state drive stores data in memory chips and has no moving parts. A hard drive stores data on spinning magnetic disks. The first and second rows are two different kinds of storage, which meets the two-media part of the rule. The third row is off site.

Two common misreadings are worth stating now:

  • A second folder on the same laptop is a second copy, but it is on the same medium and in the same place. It counts as one of the three copies only, not as the second medium or the off-site copy.
  • A cloud folder that syncs with your laptop is off site, but a sync copies changes as well as the work you want to keep. The mistakes section below shows how a sync can copy a deletion. A sync alone is not a backup.

Worked example 1: an archive and a checksum

An archive is one file that holds many files and folders. The tar command builds archives. Its letters are short commands: -c creates an archive, -z compresses it with gzip, -f gives the archive's file name, -t lists what is inside, and -x extracts it. An archive made with -z usually ends in .tar.gz.

A checksum is a short code computed from a file's contents. The sha256sum command prints a SHA-256 checksum, which is 64 characters long. The same contents always give the same code. If one byte of the file changes, the code changes too. That makes a checksum a fingerprint: you store it with the original, and later you compare it with the copy.

Create an empty folder, go into it, and run these commands. They make a small project folder, pack it into an archive, and record the archive's checksum.

mkdir -p project/notes
printf 'Chapter 1 draft\n' > project/notes/chapter1.txt
printf 'month,amount\njan,1200\n' > project/budget.csv
tar -czf project-backup.tar.gz project
tar -tzf project-backup.tar.gz
sha256sum project-backup.tar.gz > project-backup.tar.gz.sha256
sha256sum -c project-backup.tar.gz.sha256

Output:

project/
project/budget.csv
project/notes/
project/notes/chapter1.txt
project-backup.tar.gz: OK

The tar -tzf line lists the archive's contents without unpacking it, which is a quick way to see what a backup holds. The last line says that the file still matches the checksum that was saved earlier. The OK is the result that matters.

Now simulate damage. The next commands copy the archive and change one byte inside the copy. The dd command writes the letter x at byte position 100. The sed command rewrites the checksum file so that it points at the damaged copy.

cp project-backup.tar.gz damaged.tar.gz
printf 'x' | dd of=damaged.tar.gz bs=1 seek=100 conv=notrunc status=none
sed 's/project-backup.tar.gz/damaged.tar.gz/' project-backup.tar.gz.sha256 > damaged.sha256
sha256sum -c damaged.sha256
echo "exit status: $?"

Output:

damaged.tar.gz: FAILED
sha256sum: WARNING: 1 computed checksum did NOT match
exit status: 1

One changed byte is enough. The checksum no longer matches, so the copy is not identical to the original. The exit status is a number that a command returns when it finishes. Zero means success and any other number means something went wrong. A script can read this number and react to it.

Worked example 2: a script that makes and checks three copies

The commands above check one archive by hand. A script does the same steps every time and does not skip one when you are in a hurry. The script below does five things:

  1. Writes a small source folder, which stands in for your real work.
  2. Packs it into a dated archive, for example thesis-2026-11-05.tar.gz.
  3. Copies the archive to two more folders, which stand in for a second drive and an off-site copy.
  4. Restores each of the three archives into a test folder.
  5. Compares every restored file with the source, using a checksum for each file.

Save this as backup_lesson.py in an empty folder:

import hashlib
import os
import shutil
import tarfile
 
 
def sha256_of(path):
    digest = hashlib.sha256()
    with open(path, "rb") as f:
        for block in iter(lambda: f.read(65536), b""):
            digest.update(block)
    return digest.hexdigest()
 
 
def make_manifest(folder):
    manifest = {}
    for root, dirs, files in os.walk(folder):
        dirs.sort()
        for name in sorted(files):
            full = os.path.join(root, name)
            rel = os.path.relpath(full, folder)
            manifest[rel] = sha256_of(full)
    return manifest
 
 
def make_archive(source, archive_path):
    with tarfile.open(archive_path, "w:gz") as tar:
        tar.add(source, arcname=os.path.basename(source))
 
 
def restore_and_compare(archive_path, source, restore_dir):
    os.makedirs(restore_dir, exist_ok=True)
    with tarfile.open(archive_path, "r:gz") as tar:
        tar.extractall(restore_dir, filter="data")
    restored = os.path.join(restore_dir, os.path.basename(source))
    expected = make_manifest(source)
    actual = make_manifest(restored)
    problems = []
    for rel, digest in expected.items():
        if rel not in actual:
            problems.append("missing after restore: " + rel)
        elif actual[rel] != digest:
            problems.append("content differs: " + rel)
    for rel in actual:
        if rel not in expected:
            problems.append("extra file after restore: " + rel)
    return len(expected), problems
 
 
def main():
    base = os.path.join(os.getcwd(), "backup-demo")
    source = os.path.join(base, "source", "thesis")
    os.makedirs(os.path.join(source, "chapters"), exist_ok=True)
    with open(os.path.join(source, "chapters", "01-intro.txt"), "w") as f:
        f.write("Introduction draft, version 3.\n")
    with open(os.path.join(source, "chapters", "02-method.txt"), "w") as f:
        f.write("Method notes.\n")
    with open(os.path.join(source, "references.bib"), "w") as f:
        f.write("@book{smith2020, title={Example}}\n")
 
    # Copy 1 is the working folder. Copy 2 is a second drive. Copy 3 is off site.
    # Here the two other copies are folders, so the script runs on any computer.
    archive_name = "thesis-2026-11-05.tar.gz"
    work_archive = os.path.join(base, "work", archive_name)
    os.makedirs(os.path.dirname(work_archive), exist_ok=True)
    make_archive(source, work_archive)
    original_digest = sha256_of(work_archive)
    print("archive:", archive_name)
    print("sha256:", original_digest)
 
    for label, folder in [("second drive", "disk-b"), ("off site", "offsite")]:
        target_dir = os.path.join(base, folder)
        os.makedirs(target_dir, exist_ok=True)
        target = os.path.join(target_dir, archive_name)
        shutil.copy2(work_archive, target)
        copy_digest = sha256_of(target)
        status = "same" if copy_digest == original_digest else "DIFFERENT"
        print(f"copy to {label}: {status}")
 
    for label, folder in [("working", "work"), ("second drive", "disk-b"), ("off site", "offsite")]:
        archive_path = os.path.join(base, folder, archive_name)
        restore_dir = os.path.join(base, "restore-test", folder)
        count, problems = restore_and_compare(archive_path, source, restore_dir)
        if problems:
            print(f"restore from {label}: FAILED")
            for p in problems:
                print("  " + p)
        else:
            print(f"restore from {label}: OK, {count} files match")
 
 
if __name__ == "__main__":
    main()

Run it from the same folder:

python3 backup_lesson.py

Output:

archive: thesis-2026-11-05.tar.gz
sha256: 2ef2d4d53a3470ff9571158288928e2d74dbe3d8b500ca2492b0fa3325cb824f
copy to second drive: same
copy to off site: same
restore from working: OK, 3 files match
restore from second drive: OK, 3 files match
restore from off site: OK, 3 files match

Your sha256 line will differ, because the archive stores file modification times, which change each time the script runs. The three OK lines are what matter.

Here is what the script does in plain words. make_manifest builds a list of every file in a folder, with a checksum for each one. A manifest is that list. restore_and_compare unpacks an archive into a test folder, builds a manifest of the restored files, and compares the two lists. It reports three kinds of problem: a file that is missing, a file whose contents differ, and a file that appears after the restore but was not in the source.

The filter="data" argument tells tarfile to refuse entries that would write outside the restore folder. Python 3.12 and later accept this argument, and so do recent patch releases of 3.8 to 3.11. Without it, a damaged or hostile archive could place files in other parts of your computer.

In a real setup, the second and third folders would be a path to a USB drive and a path to a cloud sync folder. The script shows the logic. It does not make a true off-site copy by itself.

Worked example 3: catching an old copy

A restore test is only useful if it compares against what you expect. Suppose Asha changes a chapter after the backup was made, and the external drive still holds the older archive. The check should report that difference. Run this in the same folder, after the script above has run once:

echo "New intro, not in the backup." > backup-demo/source/thesis/chapters/01-intro.txt
python3 -c "
import backup_lesson as b
print(b.restore_and_compare('backup-demo/disk-b/thesis-2026-11-05.tar.gz', 'backup-demo/source/thesis', 'backup-demo/check'))
"

Output:

(3, ['content differs: chapters/01-intro.txt'])

The result is a pair. The first number is how many files the source folder has, which is 3. The second item lists the problems. The archive itself is intact, since it unpacks without error. It is simply older than the working folder. The report points to the one file that differs, which tells Asha to make a new backup before she trusts the older copy for anything. An archive that unpacks without error is not automatically the version you wanted.

Mirror sync is not a backup

A sync tool keeps two folders the same. rsync is a common command for this. A mirror is a folder that a sync keeps identical to another folder. A mirror is useful, but it is not a backup, because it copies deletions and bad edits too. The commands below show that.

Create a new empty folder and run:

mkdir -p work/thesis/chapters
printf 'Intro v1\n' > work/thesis/chapters/01-intro.txt
printf 'Method v1\n' > work/thesis/chapters/02-method.txt
rsync -a --delete work/ mirror/
find mirror -type f | sort

Output:

mirror/thesis/chapters/01-intro.txt
mirror/thesis/chapters/02-method.txt

The -a option is archive mode, which keeps file times and permissions. The --delete option removes files from the mirror that no longer exist in work. Now make two mistakes in the working folder: delete one chapter, and overwrite the other by accident. Then sync again:

printf 'oops\n' > work/thesis/chapters/01-intro.txt
rm work/thesis/chapters/02-method.txt
rsync -a --delete work/ mirror/
find mirror -type f | sort
cat mirror/thesis/chapters/01-intro.txt

Output:

mirror/thesis/chapters/01-intro.txt
oops

The mirror now matches the mistake exactly. The method chapter is gone, and the intro has lost its text. If this mirror were the only other copy, both losses would be permanent. Running the same sync without --delete keeps the deleted chapter, but the overwritten intro still spreads to the mirror. Both options copy the bad edit.

A backup protects you because it is a separate, dated record. The archive name in worked example 2 contains a date, so an archive made on a later date does not overwrite the earlier one. A backup tool that keeps several dated versions, or an archive stored once a day, gives you a way back to a version before the mistake.

Common mistakes

Trusting the backup without a restore test. Making an archive proves only that the command finished. Asha's damaged external drive shows that a copy can exist and still be unusable. Run a restore into an empty folder at least once for each kind of copy you rely on, then compare the result with the original.

Ignoring the exit status of a backup command. A failed tar prints an error but a script that does not check can carry on as if the backup worked. Here is the error from a missing file:

tar: project/nothere.txt: Cannot stat: No such file or directory
tar: Exiting with failure status due to previous errors

Check the result on the same line:

tar -czf bad.tar.gz project/nothere.txt || echo "backup failed"

Output:

tar: project/nothere.txt: Cannot stat: No such file or directory
tar: Exiting with failure status due to previous errors
backup failed

The || means "run the second command only if the first one fails". The echo line then prints a clear message you can see.

Using a sync as a backup. The mirror example above shows how a deletion or an overwrite spreads. A sync is useful for keeping two machines in step. For protection against mistakes, keep dated archives.

Pointing a restore at a path that does not exist. A typing error in a path stops the script, and Python reports it with a traceback, the list of lines that led to the error. Here is the last line of the error from a wrong archive name:

FileNotFoundError: [Errno 2] No such file or directory: 'backup-demo/work/thesis-2026-1.tar.gz'

The last line names the file that could not be found. Check the spelling of the path against ls output before you blame the tool.

Reading a checksum failure as a rounding problem. A FAILED line from sha256sum -c does not mean the file is almost right. A checksum either matches or it does not. Do not open the failed copy and save it over the good one. Find another copy, restore from it, and check again.

Practice

Exercise 1. A student keeps the working thesis folder on the laptop, a second folder called thesis-copy on the same laptop disk, and a USB drive that lives in the same bag as the laptop. Which part of the 3-2-1 rule does this plan miss?

Show the answer The plan misses the off-site copy. The second folder is on the same disk, so it does not count as a second medium. The USB drive counts as a second medium, but it travels with the laptop, so it is not off site. A cloud account or a drive kept in another place would meet the rule.

Exercise 2. Write one line of shell that creates project-backup.tar.gz from the folder project, and prints backup failed if the archive could not be made.

Show the answer tar -czf project-backup.tar.gz project || echo "backup failed"

The command runs tar first. If tar returns a non-zero exit status, the echo runs and prints the message.

Exercise 3. Using the archive from worked example 1, restore project-backup.tar.gz into a new folder called restored, then compare the restored folder with the original project folder. What output means that the restore matches?

Show the answer Run these commands in the folder that holds project and the archive:

mkdir -p restored
tar -xzf project-backup.tar.gz -C restored
diff -r project restored/project
echo "diff exit status: $?"

When the copy matches, diff prints nothing and the exit status is 0. Any line that diff prints names a difference.

Exercise 4. Explain why the mirror lost the method chapter after the second rsync in the mirror section, even though nobody edited the mirror folder.

Show the answer The --delete option tells rsync to make the mirror match the working folder exactly. The working folder had no 02-method.txt after rm, so rsync removed the copy in the mirror. Nobody touched the mirror, which is the point: a mirror follows the source, including its mistakes.

Exercise 5. A restore test reports (3, ['content differs: references.bib']). The archive unpacks without error. What does the report mean, and what should you do first?

Show the answer The restored file references.bib has different contents from the file in the working folder. The archive is readable, so it is either older than the working folder or it was damaged after it was made. Do not overwrite the working folder with it. Check the other copies, restore from one of them, and run the comparison again. If every copy differs, make a new backup from the working folder only after you confirm the working file is correct.

Summary

  • A backup is a copy you can restore. A copy you have never restored is a guess.
  • The 3-2-1 rule means three copies, on two kinds of storage, with one copy off site.
  • A .tar.gz archive packs a folder into one file. A sha256sum checksum shows whether a copy still matches the original.
  • A restore test unpacks each copy into a new folder and compares every file with the source.
  • A sync mirror copies deletions and bad edits too, so it does not protect you from mistakes on its own. Keep dated archives.
  • Check the exit status or the error message of every backup command, so a failure never looks like a success.
Check yourself: Leaks and backups (8 questions) →

Stuck? Ask the tutor

Ask about anything in this lesson. Answers are short and use simple words.

Sign in to ask the tutor.

← Secrets in code and git history