Backing up a Raspberry Pi to a NAS with rsync (and the 3 days it failed without me noticing)

My nightly backup script from the Pi to a Synology over SMB: mirror, 30-day history and Docker volumes. And why it stopped working without anyone noticing.

Iván· Published on · 4 minLeer en español →

My Raspberry Pi is the server for almost everything at home: password manager, Git server, a workout app, dashboards… If the card or disk dies, I want to be able to rebuild it. So every night at 3:30 a script copies everything important to a Synology NAS.

Here’s how it’s built and, above all, how it failed for three days without me noticing.

What gets copied and where

The NAS only exposes SMB (Windows shared folders). No SSH or rsync on the NAS side, so the Pi mounts the shared folder and works on it as if it were a local disk.

On the NAS the backup looks like this:

Backups/pi/
├── actual/              ← today's mirror
│   └── volumenes/       ← Docker volumes as .tar.gz
└── historico/
    ├── 2026-09-23/      ← what changed or was deleted that day
    └── 2026-09-24/

The script, step by step

1. Mount the NAS

SHARE="//nas-casa.local/Backups"
mount -t cifs "$SHARE" /mnt/nas-backups \
  -o credentials=/root/.smb-nas,vers=3.0,uid=0,gid=0,file_mode=0600,dir_mode=0700

The NAS username and password live in /root/.smb-nas with 600 permissions, not in the script. And the NAS has a dedicated user that can only write to the backup folder. If someone breaks into the Pi, they don’t get the rest of the NAS.

2. Save the system “state”

Before copying files, the script records how the Pi is set up: which containers, which volumes, which packages, the cron jobs and a .tar.gz of /etc. If I ever have to rebuild it from scratch, that’s the recipe.

docker ps -a --format '{{.Names}}\t{{.Image}}\t{{.Ports}}' > estado/docker-ps.txt
dpkg --get-selections > estado/paquetes.txt
tar czf estado/etc.tar.gz /etc

3. Docker volumes

Many containers keep their data in Docker volumes, which aren’t regular folders. To copy them, I start a tiny container (Alpine) that mounts the volume read-only and archives it:

docker run --rm -v "$VOLUME":/v:ro -v "$DEST":/b alpine \
  tar czf "/b/${CONTAINER}.tar.gz" -C /v .

For databases, a proper dump is better. For example, the password manager has its own database backup command, and I run it first so the file is consistent.

4. The mirror with history

Here’s the good part. rsync only copies what changed, and with --backup-dir it stores in a dated folder everything it overwrote or deleted that day:

rsync -rlt --delete \
  --backup --backup-dir="/mnt/nas-backups/pi/historico/$(date +%F)" \
  --exclude='node_modules' --exclude='*.log' \
  "${SOURCES[@]}" /mnt/nas-backups/pi/actual/
  • --delete keeps actual/ an exact mirror.
  • Whatever is deleted or changed isn’t lost: it moves to that day’s history.
  • One find line removes history older than 30 days.

Since SMB doesn’t keep Linux owners or permissions, I don’t ask for them (-rlt instead of -a).

5. Report success

At the end, if everything went well, the script calls a push monitor in Uptime Kuma. If that call doesn’t arrive one night, Kuma marks the backup as failed.

The three days it failed without me noticing

From 20 to 22 September, the log said this every night:

2026-09-20 03:30:07 ERROR: no se pudo montar //192.168.1.60/Backups
2026-09-21 03:30:07 ERROR: no se pudo montar //192.168.1.60/Backups
2026-09-22 03:30:07 ERROR: no se pudo montar //192.168.1.60/Backups

(“Could not mount”.) The NAS had changed IP address. The router assigns it by DHCP and, after a restart, gave it a new one. The script looked for it at the old address, couldn’t find it and gave up.

The worst part wasn’t the failure but that nobody noticed for three days. Uptime Kuma knew: the backup monitor was red. But it had no notifications configured. An alarm without a siren.

I fixed two things:

  1. Mount the NAS by name (nas-casa.local) instead of by IP. The Pi resolves it via mDNS even when the address changes.
  2. Set up Telegram alerts in Uptime Kuma, so a failure like this reaches my phone the same night. I explain it step by step in another article.

The proper fix is giving the NAS a fixed IP with a DHCP reservation on the router. It’s on my to-do list.

Another trap: deleting something that’s still on the list

Recently I uninstalled a program from the Pi and deleted its folder. That folder was still in the script’s source list. If I hadn’t spotted it, rsync would have exited with an error that night (a missing source) and the backup would have been marked as failed.

Rule: when you remove something from the system, remove it from the backup script too. And the other way round: if you install something with important data, add it.

Quick checklist

  • NAS credentials outside the script, with 600 permissions.
  • A NAS user that can only write to the backup folder.
  • NAS mounted by name, or given a fixed IP.
  • Docker volumes and database dumps included.
  • History of previous days, not just the mirror.
  • A monitor that alerts you if the backup doesn’t arrive. And that actually alerts.
  • Test restoring something now and then. A backup you’ve never restored is a hope, not a backup.
Mini quiz

Did it stick?

Three quick questions. Each right answer is worth 10 XP.

  1. Why did the backups fail three nights in a row?
  2. What does rsync's --backup-dir option do in this script?
  3. What happens if you delete a folder that is still in rsync's source list?
  • #backup
  • #rsync
  • #raspberry-pi
  • #synology
  • #docker
  • #cron
Esc