{
  "$type": "site.standard.document",
  "bskyPostRef": {
    "cid": "bafyreiajjmekkptkkmols46ggwfolbv36u26rpsbiutmdxnqzpeluj5g6y",
    "uri": "at://did:plc:25rdn5elo5izoxrmtis34zuk/app.bsky.feed.post/3mozgif5xk522"
  },
  "coverImage": {
    "$type": "blob",
    "ref": {
      "$link": "bafkreibc3k35wpyw6j4o2zdqvhvgpgxolwvoaqcaunaktlhshobj5fhnrq"
    },
    "mimeType": "image/webp",
    "size": 283004
  },
  "path": "/tarrantro/linux-backup-made-simple-automate-incremental-system-snapshots-with-restic-4kp9",
  "publishedAt": "2026-06-24T07:20:24.000Z",
  "site": "https://dev.to",
  "tags": [
    "linux",
    "bash",
    "devops",
    "tutorial"
  ],
  "textContent": "> A single Bash script, a USB drive, and 30 seconds a day. No cloud. No subscriptions. No excuses.\n\nYou have spent months tweaking your Linux environment. The perfect i3 config, the custom kernel parameters, the Docker containers you curated one by one. Then one day — a power surge fries your NVMe. A bad `rm -rf`. A failed `dist-upgrade`. And it's all gone.\n\nMost Linux users either don't back up at all, or rely on manual rsync and tar commands they run twice a year. The good news: you can set up fully automated, incremental, encrypted system snapshots with a single script and a USB drive — no cloud subscriptions, no complex configuration, no vendor lock-in.\n\nIn this guide I'll show you how to use **restic** — an open-source backup tool with built-in deduplication, client-side encryption, and snapshot management — paired with a Bash script that handles the real-world edge cases for you: detecting external mounts to avoid wasteful backups, skipping caches that would bloat your repository, and initializing itself on first run.\n\n##  Why Restic?\n\nMost Linux users fall into one of two camps: they either don't back up at all, or they use rsync / tar because that's what they have always used. Both are risky for different reasons. Here is how restic compares:\n\nTool | Incremental | Dedup | Encryption | Snapshot Mgmt | Restore UX\n---|---|---|---|---|---\nrsync | partial | no | no | no | manual\ntar | no | no | no | no | manual\nduplicity | yes | no | yes | basic | slow on long chains\nborg | yes | yes | yes | yes | requires borg binary\ntimeshift | yes | no | no | yes | GUI-focused, tied to local disk\n**restic** | **yes** | **yes** | **yes** | **yes** | **single binary, any target**\n\nHere is why these differences matter in practice:\n\n###  True Incremental Backups\n\nRestic chunks your files into content-addressed blobs. When you run a backup, it only uploads chunks it hasn't seen before. The second backup of a 200GB system where you changed three config files? It takes seconds, not hours.\n\n###  Global Deduplication\n\nHave the same 2GB ISO sitting in three different directories? Restic stores it once. The same Python virtual environment duplicated across five projects? One copy. This matters especially when your USB drive is finite.\n\n###  Client-Side Encryption\n\nYour backup repository is encrypted before a single byte leaves your machine. Lose the USB drive on a train? Without the password, the data is meaningless noise. Restic uses AES-256 in CTR mode with Poly1305-AES for authentication.\n\n###  Snapshots, Not Archives\n\nEvery backup is a named, timestamped snapshot. You can list them, diff them, mount them as a FUSE filesystem, or restore individual files from any point in time. This is closer to what you would expect from a ZFS snapshot than a tarball.\n\n###  Single Static Binary\n\nNo daemon. No database. No kernel module. Restic is one Go binary. Copy it to any machine and it works. This also makes it trivially deployable in rescue environments — just `scp` the binary and go.\n\n##  The Script: Automation with Intelligence\n\nA backup tool is only as good as your willingness to run it. If it requires you to remember which directories to exclude, or mounts to skip, or flags to pass, you will eventually stop running it.\n\nThe script tackles four real-world problems:\n\n###  1. Sensible Exclusions Out of the Box\n\nThe script ships with a curated set of exclusions that skip directories you would never want in a system backup:\n\n\n\n    EXCLUDES=(\n        --exclude /proc --exclude /sys --exclude /dev\n        --exclude /tmp --exclude /run --exclude /mnt --exclude /media\n        --exclude /var/tmp --exclude /lost+found --exclude /.snapshots\n        --exclude '/home/*/.cache'\n        --exclude '/home/*/.npm'\n        --exclude '/home/*/.local/share/Trash'\n        --exclude /var/cache/apt/archives\n    )\n\n\nA few of these deserve explanation:\n\n  * `/home/*/.cache` — This is the big one. Browser caches, Go build artifacts, pip wheels, and a thousand other things apps dump here. On a well-used system this can easily be 5–10 GB of data you will never need to restore.\n  * `/home/*/.npm` — Node.js package cache. A single `npm install` rebuilds it.\n  * `/home/*/.local/share/Trash` — The trash bin. You deleted it once already.\n  * `/var/cache/apt/archives` — Downloaded `.deb` packages. `apt update` fetches fresh ones.\n\n\n\nUsing bash arrays (`\"${EXCLUDES[@]}\"`) instead of a plain string ensures patterns like `/home/*/.cache` survive shell expansion intact and are interpreted correctly by restic.\n\n###  2. Auto-Detect External Mounts\n\nOn top of the static exclusion list, the script dynamically detects remote filesystems so you never accidentally pull a NAS into your backup. It parses `/proc/mounts` and appends matches to the same array:\n\n\n\n    while read -r _ mp fs_type _; do\n        case \"$fs_type\" in\n            cifs|nfs|nfs4|fuse.sshfs|fuse.rclone)\n                EXCLUDES+=(--exclude \"$mp\")\n                ;;\n        esac\n    done < /proc/mounts\n\n\nThis means you can mount and unmount NAS shares, add new ones, or switch between SMB and NFS, and the script will never accidentally pull them in. Each detected mount is logged so you can verify what was excluded.\n\n###  3. First Run vs. Daily Run\n\nThe script has exactly two paths through its logic:\n\n  * **No repository yet** → initialize a new encrypted restic repository on the USB drive, then run the first backup.\n  * **Repository exists** → run an incremental backup. Only changed chunks are transmitted.\n\n\n\nNo separate init step. No configuration file to maintain. Plug in the USB, run the script, and it figures out what to do.\n\n\n\n    if [ ! -f \"${REPO}/config\" ]; then\n        restic init --repo \"$REPO\"\n    fi\n    restic backup / --repo \"$REPO\" \"${EXCLUDES[@]}\"\n\n\n###  4. Format Flexibility\n\nThree modes for different needs:\n\n\n\n    ./backup.sh           # incremental — what you run every day\n    ./backup.sh --dry     # preview what would change, no data written\n    ./backup.sh --full    # force re-read of all files\n\n\nThe `--dry` mode is particularly useful before a system upgrade — see exactly which files have changed since your last backup. The `--full` mode is for when you suspect filesystem corruption or bit rot and want a fresh scan of every file.\n\n##  The Full Script\n\nCopy it, adjust the two variables at the top, and you are ready to go:\n\n\n\n    #!/bin/bash\n    # ============================================================\n    # Restic incremental backup script — local machine\n    # Usage: ./backup.sh          # incremental backup\n    #        ./backup.sh --dry    # preview changes\n    #        ./backup.sh --full   # force full scan\n    # ============================================================\n    set -euo pipefail\n\n    # ---- Config: customize these two lines ----\n    USB=\"/media/$USER/backup-disk\"    # USB mount point\n    REPO=\"${USB}/my-laptop\"           # subdirectory (unique per machine)\n\n    TIMESTAMP_FILE=\"${REPO}/LAST_BACKUP\"\n    LOGFILE=\"${HOME}/.local/state/backup.log\"\n\n    log() { echo \"[$(date '+%Y-%m-%d %H:%M:%S')] $*\" | tee -a \"$LOGFILE\"; }\n    die()  { log \"ERROR: $*\"; exit 1; }\n\n    mkdir -p \"$(dirname \"$LOGFILE\")\"\n\n    # ---- Dependencies ----\n    command -v restic >/dev/null 2>&1 || die \"restic not installed. Run: sudo apt install restic\"\n\n    # ---- USB mount check ----\n    mountpoint -q \"$USB\" || die \"USB not mounted: $USB\"\n\n    # ---- Build exclude list ----\n    EXCLUDES=(\n        --exclude /proc --exclude /sys --exclude /dev\n        --exclude /tmp --exclude /run --exclude /mnt --exclude /media\n        --exclude /var/tmp --exclude /lost+found --exclude /.snapshots\n        --exclude '/home/*/.cache'\n        --exclude '/home/*/.npm'\n        --exclude '/home/*/.local/share/Trash'\n        --exclude /var/cache/apt/archives\n    )\n\n    # Auto-detect CIFS / NFS / SSHFS external mounts\n    while read -r _ mp fs_type _; do\n        case \"$fs_type\" in\n            cifs|nfs|nfs4|fuse.sshfs|fuse.rclone)\n                EXCLUDES+=(--exclude \"$mp\")\n                log \"Excluding external mount: $mp ($fs_type)\"\n                ;;\n        esac\n    done < /proc/mounts\n\n    # ---- Repo init ----\n    if [ ! -f \"${REPO}/config\" ]; then\n        log \"Repo not found, initializing restic repository...\"\n        restic init --repo \"$REPO\" || die \"Repo init failed\"\n        log \"Repo initialized.\"\n    else\n        log \"Repo exists, running incremental backup.\"\n    fi\n\n    # ---- Arguments ----\n    RESTIC_ARGS=(\n        --verbose\n        --limit-upload 50000\n        --limit-download 50000\n    )\n\n    case \"${1:-}\" in\n        --dry|--dry-run)\n            log \"=== DRY-RUN mode ===\"\n            restic backup / \\\n                --repo \"$REPO\" \\\n                \"${EXCLUDES[@]}\" \\\n                \"${RESTIC_ARGS[@]}\" \\\n                --dry-run\n            log \"Dry-run complete.\"\n            exit 0\n            ;;\n        --full)\n            log \"Forcing full scan.\"\n            RESTIC_ARGS+=(--force)\n            ;;\n    esac\n\n    # ---- Backup ----\n    log \"Starting backup / → $REPO ...\"\n    restic backup / \\\n        --repo \"$REPO\" \\\n        \"${EXCLUDES[@]}\" \\\n        \"${RESTIC_ARGS[@]}\" \\\n        2>&1 | tee -a \"$LOGFILE\"\n\n    # ---- Timestamp ----\n    date '+%Y-%m-%d %H:%M:%S' > \"$TIMESTAMP_FILE\"\n    log \"Backup complete. Timestamp: $(cat \"$TIMESTAMP_FILE\")\"\n\n    # ---- Maintenance reminder ----\n    SNAP_COUNT=$(restic snapshots --repo \"$REPO\" 2>/dev/null | grep -c '^[a-f0-9]' || echo \"?\")\n    log \"Snapshot count: $SNAP_COUNT\"\n    log \"Tip: restic forget --repo $REPO --keep-daily 14 --keep-monthly 6 --keep-yearly 5 --prune\"\n\n\n##  Step-by-Step Setup\n\n###  Prerequisites\n\n  * A USB drive (preferably SSD) with enough capacity. Format it as ext4 for best performance and to preserve Linux permissions.\n  * restic installed on your machine (`sudo apt install restic` on Debian/Ubuntu).\n\n\n\n###  Step 1: Find Your USB\n\n\n    lsblk -o NAME,SIZE,TYPE,MOUNTPOINT,LABEL\n\n\nLook for your USB drive in the output. Note the mount point — typically `/media/$USER/<label>`. Set the `USB` variable in the script to match.\n\n###  Step 2: Save the Script\n\nCopy the script above into a file and make it executable:\n\n\n\n    chmod +x backup.sh\n\n\nIf you have multiple machines, give each a different `REPO` name (e.g., `my-laptop`, `my-desktop`, `my-server`). This keeps their backups in separate subdirectories on the same USB drive.\n\n###  Step 3: First Run\n\n\n    ./backup.sh\n\n\nRestic will prompt you to set a password for the repository. **Do not lose this password.** Write it down. Store it in your password manager. Without it, the backup is unrecoverable. There is no \"reset password\" button.\n\nThe first run will take a while — it reads your entire filesystem. On a 200GB SSD over USB 3.0, expect roughly 20–40 minutes. Subsequent runs take seconds to a few minutes.\n\n###  Step 4: Verify\n\n\n    restic snapshots --repo \"$USB/my-laptop\"\n\n\nYou should see your first snapshot, timestamped. Run `./backup.sh` again a few hours later and you will see a second snapshot appear. The data stored on disk will barely grow because only changed files were uploaded.\n\n###  Step 5: Automate (Optional but Recommended)\n\nAdd a cron job or systemd timer:\n\n\n\n    # crontab -e\n    0 20 * * * /home/$USER/scripts/backup.sh >> /home/$USER/.local/state/backup.log 2>&1\n\n\nThis runs the backup every evening at 8 PM. Since the script uses restic's `--limit-upload` flag, it won't saturate your USB bandwidth even on slower drives.\n\n##  Maintenance and Restore\n\n###  Cleaning Up Old Snapshots\n\nSnapshots accumulate. After a few months, you might have hundreds. Restic's forget policy lets you keep a sensible rotation:\n\n\n\n    restic forget --repo \"$USB/my-laptop\" \\\n        --keep-daily 14 \\\n        --keep-monthly 6 \\\n        --keep-yearly 5 \\\n        --prune\n\n\nThis keeps: every snapshot from the last 14 days, one per month for the last 6 months, and one per year for the last 5 years. Everything else is pruned and the underlying data blobs are garbage-collected.\n\n###  Restoring Your System\n\nFull system restore to a new disk:\n\n\n\n    restic restore latest --repo \"$USB/my-laptop\" --target /mnt/new-disk\n\n\nSingle file restore:\n\n\n\n    restic restore latest --repo \"$USB/my-laptop\" --target / --include /home/$USER/.bashrc\n\n\nRestore from a specific date:\n\n\n\n    restic snapshots --repo \"$USB/my-laptop\"              # find the snapshot ID\n    restic restore <snapshot-id> --repo \"$USB/my-laptop\" --target /path\n\n\n###  Checking Repository Health\n\nRun periodically (monthly is fine):\n\n\n\n    restic check --repo \"$USB/my-laptop\"               # quick structural check\n    restic check --repo \"$USB/my-laptop\" --read-data   # thorough, reads all data\n\n\n###  Mounting Snapshots as a Filesystem\n\nYou can browse your snapshots without restoring:\n\n\n\n    restic mount --repo \"$USB/my-laptop\" /mnt/restic\n    ls /mnt/restic/snapshots/\n\n\nThis is invaluable for comparing versions or grabbing a file you deleted last week without doing a full restore.\n\n##  Summary\n\nYou don't need a complex backup strategy. You need one USB drive, one script, and one habit.\n\nRestic gives you enterprise-grade backup features — deduplication, encryption, snapshot management — in a tool that is genuinely simple to use. The script wraps it in automation that handles the real-world edge cases: external mounts, cache directories, first-run initialization, and daily incrementals.\n\nSet it up once. Run it often. The next time your system breaks, you will be back up and running in the time it takes to restore a snapshot, not the time it takes to rebuild from memory.",
  "title": "Linux Backup Made Simple: Automate Incremental System Snapshots with Restic"
}