Tutorial5 min read

Bulletproof Off-Site Backup for Your Seedbox and Media Server (Borg + rclone + ZFS)

Running Jellyfin, Nextcloud, or Immich on your seedbox or home server? Here's a practical, tested backup strategy using Borg and rclone that won't let you down.

By Seeding.Top Team|

Bulletproof Off-Site Backup for Your Seedbox and Media Server

If you're running Jellyfin, Nextcloud, Immich, or any Docker-based stack on a seedbox or home server, your backup strategy is either saving you or waiting to fail you. Most people either skip backups entirely or do something half-baked that won't survive a real disaster.

This guide covers a practical, layered approach: Borg for local/remote deduplication + rclone for cloud offsite, with specific notes for ZFS users and Docker volume handling.


What You Actually Need to Back Up

Before touching a single command, get clear on what matters:

Data TypeExamplesPriority
Application databasesNextcloud DB, Immich PostgresCritical
App config/metadataJellyfin metadata, Sonarr DBHigh
User-uploaded contentImmich photos, Nextcloud filesHigh
Docker volumesAll of the above, usually in /var/lib/docker/volumes/High
Media libraryMovies, TV shows, musicLow — rerippable/re-downloadable

Don't waste backup space on your media library. If you're on a seedbox or private tracker, you can re-download it. Focus your backup budget on the irreplaceable stuff: databases, configs, and personal uploads.


The Stack: Borg + rclone

  • BorgBackup — deduplicating, compressed, encrypted backups. Ideal for incremental daily snapshots.
  • rclone — syncs Borg repos (or anything) to cloud storage: Backblaze B2, Wasabi, S3, or even another seedbox via SFTP.

Together they give you: local fast restore + off-site disaster recovery.


Step 1: Handle Databases Before You Back Up Anything

Never back up a running database by just copying files. You'll get a corrupted or inconsistent snapshot. Always dump first.

Nextcloud (with AIO)

# Stop AIO stack, run the built-in backup
docker exec -it nextcloud-aio-mastercontainer php occ maintenance:mode --on
# Then proceed with Borg

Or use Nextcloud AIO's built-in backup feature which handles this for you.

Immich (Postgres)

docker exec -t immich_postgres pg_dumpall -c -U postgres > /backup/immich_dump_$(date +%Y%m%d).sql

Jellyfin

Jellyfin doesn't use a traditional DB — its metadata lives in the config directory. Just stop the container before backing up:

docker stop jellyfin
# run borg
docker start jellyfin

Sonarr / Radarr / Prowlarr

These use SQLite. Back up the entire /config directory while the container is stopped, or use the built-in System → Backup feature which creates a safe .zip.


Step 2: Set Up a Borg Repository

Install Borg on your server:

# Debian/Ubuntu
sudo apt install borgbackup

# Or latest via pip
pip install borgbackup

Initialize a repo (with encryption):

borg init --encryption=repokey /mnt/backup/borg-repo

Save your passphrase somewhere safe — without it, your backups are unrecoverable.


Step 3: The Backup Script

Here's a practical script that stops containers, dumps databases, runs Borg, then restarts everything:

#!/bin/bash
set -e

BORG_REPO="/mnt/backup/borg-repo"
export BORG_PASSPHRASE="your-passphrase-here"
BACKUP_DIRS="/opt/docker/configs /backup/db-dumps"
LOG="/var/log/borg-backup.log"

echo "=== Backup started: $(date) ===" >> $LOG

# 1. Dump databases
docker exec immich_postgres pg_dumpall -c -U postgres > /backup/db-dumps/immich_$(date +%Y%m%d).sql

# 2. Stop containers that need consistent state
docker stop jellyfin sonarr radarr prowlarr

# 3. Run Borg
borg create \
  --verbose \
  --filter AME \
  --list \
  --stats \
  --show-rc \
  --compression lz4 \
  --exclude-caches \
  ::'{hostname}-{now}' \
  $BACKUP_DIRS \
  2>> $LOG

# 4. Prune old backups (keep 7 daily, 4 weekly, 3 monthly)
borg prune \
  --list \
  --prefix '{hostname}-' \
  --keep-daily 7 \
  --keep-weekly 4 \
  --keep-monthly 3 \
  2>> $LOG

# 5. Restart containers
docker start jellyfin sonarr radarr prowlarr

echo "=== Backup finished: $(date) ===" >> $LOG

Schedule it with cron:

# Run at 3am daily
0 3 * * * /opt/scripts/borg-backup.sh

Step 4: Off-Site Sync with rclone

A local Borg repo is not a backup — it's on the same machine that can fail. Push it off-site.

Configure rclone with your preferred backend (Backblaze B2 is cheap at ~$6/TB/month):

rclone config
# Follow prompts to add b2, s3, wasabi, or sftp

Sync your Borg repo after each backup:

rclone sync /mnt/backup/borg-repo remote:my-bucket/borg-repo \
  --transfers 4 \
  --checkers 8 \
  --fast-list \
  --log-file /var/log/rclone-backup.log

Add this to the end of your backup script. Total off-site cost for configs + DB dumps (typically under 50GB) is under $0.30/month on B2.


ZFS Users: Snapshots Are Not Backups

If you're running a ZFS pool, you have snapshots — but snapshots on the same pool don't protect against drive failure, accidental deletion propagating, or pool corruption. Use them as a fast local rollback layer, not a substitute for Borg + rclone.

Useful ZFS snapshot automation: sanoid

# Install sanoid
sudo apt install sanoid

# Configure /etc/sanoid/sanoid.conf
[data/docker]
  use_template = production

[template_production]
  frequently = 0
  hourly = 24
  daily = 30
  monthly = 3
  autosnap = yes
  autoprune = yes

ZFS snapshots + Borg + rclone = a genuinely resilient setup.


Testing Your Restore (Do This Now)

A backup you've never tested is a backup you don't have. Run this quarterly:

# List available archives
borg list /mnt/backup/borg-repo

# Extract a specific archive to a test location
borg extract --dry-run /mnt/backup/borg-repo::your-archive-name

# Actually extract to /tmp/restore-test
borg extract /mnt/backup/borg-repo::your-archive-name --target /tmp/restore-test

For databases, restore the dump to a test container and verify the data looks right.


Practical Takeaway

The minimum viable backup for a Jellyfin/Nextcloud/Immich stack:

  1. Daily DB dumps to a local directory
  2. Borg backup of configs + dumps with 7-day retention
  3. rclone sync of the Borg repo to Backblaze B2 or Wasabi
  4. Monthly restore test — actually extract something and check it

Total cost: ~$0–5/month depending on data size. Time to set up: 2–3 hours. Time you'll save when something breaks: priceless.

Don't be the person who discovers their backup was broken the day they need it.

#backup#borg#rclone#jellyfin#zfs#self-hosting#docker#seedbox