Andy Stanish

July 2, 2026

Backing Up a Live Raspberry Pi SD Card

I've got a Raspberry Pi that runs around the clock, and its entire world lives on one SD card. SD cards don't fail gracefully — it's a question of when, not if. So I wanted a hands-off nightly backup. The catch: the Pi needs to stay up, so I can't unmount the card to copy it cleanly. backup.sh clones the live card, block-for-block, onto a second SD card with dd — no unmount required.

The script and full README live on GitHub.

How it works

A sync flushes filesystem buffers first, then dd copies the source device to the destination device with conv=noerror,syncnoerror so a flaky read doesn't abort the whole run, and sync so bad blocks get padded and every partition offset stays aligned.

Because the source is never unmounted, the result is a crash-consistent snapshot — the equivalent of yanking the power — not a clean one. In practice that means the backup card may want an fsck on first boot, which the Pi's kernel runs automatically. Good enough to get back on my feet if the primary card dies.

The scary part: pointing dd at the right disk

dd is a loaded gun. It overwrites the destination unconditionally, and kernel names like /dev/sdb are not stable — a re-plug or a reboot can quietly reassign them. Point it at the wrong disk and that disk is gone. Same lesson I wrote up for my encrypted drive, where I learned it the hard way — I unplugged an SD-card reader, rebooted, and the boot-time unlock came up with nothing to grab because sda had quietly become sdb. Never trust /dev/sdX.

So the script pins each card by its filesystem UUID and resolves that UUID to the correct whole-disk device at run time — the UUID always wins over any device name. The real UUIDs live in a gitignored .env on the Pi, never in the repo:

SOURCE_UUID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
DEST_UUID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx

Guard rails

Since one wrong device means data loss, the script refuses to write a single byte if any of these fail:

Paranoid? Sure. But dd doesn't do second chances.

And there's a dry run that prints the exact dd it would execute and writes nothing:

sudo bash backup.sh --dry-run   # prints the dd, writes nothing
sudo bash backup.sh             # the real thing

Scheduling

It runs from root's crontab, nightly at 3 AM when the box is quiet:

0 3 * * * /path/to/backup.sh >> /var/log/pi-sd-backup.log 2>&1

Not fancy, but it means a dead SD card costs me a card swap and a reboot instead of a weekend of reinstalling and reconfiguring from memory. This is living documentation, so it'll grow as the setup does.