Immich Backup Automation Script, the full Immich backup with all data including photos and videos. Although Immich offers integrated backup functions, these only back up the database – not your valuable photos and videos! For a truly complete backup, you need both.
| Component | Contains | Built-in Backup? |
|---|---|---|
| PostgreSQL Database | Metadata, albums, face recognition, tags | ✅ Yes (daily dumps) |
| Media (Photos/Videos) | Your original files | ❌ No |
The golden rule: Back up the database first, then the media. This avoids “dead links” during restoration – photos that exist in the database but are physically missing. The time gap between both backups should be minimal.
Our script does exactly that: First the database, then the media – automatically, reliably, and space-efficiently.
Video: Immich Backup Automation Script | Full Automatic Backup
Language: 🇩🇪|🇬🇧
☝️ Use YouTube subtitles for all languages.
Installing rsync on Linux
# Install rsync (Ubuntu/Debian)
sudo apt update
sudo apt install rsync -y
# Verify installation
rsync --version
Script Description
The script is located in your docker-compose.yml directory (e.g., /mnt/d/Visual Edit/immich/backup_immich.sh). The main configuration areas:
Configuration (adjust to your needs!)
# ---------- CONFIGURATION ----------
SOURCE="/mnt/d/Visual Edit/immich/medien/"
DEST_BASE="/mnt/d/backups/immich"
LOG="$DEST_BASE/backup.log"
# Retention period (in days)
KEEP_DAILY=7 # Keep daily backups for 7 days
KEEP_WEEKLY=30 # Keep weekly backups for 30 days (approx. 4 weeks)
KEEP_MONTHLY=365 # Keep monthly backups for 365 days (1 year)
# PostgreSQL Container
DB_CONTAINER="immich_postgres"
DB_USER="postgres"
Intelligent Backup Types
The script automatically distinguishes:
| Type | When? | Example Name |
|---|---|---|
| daily | Every day except Sunday/first of month | daily-2026-03-10 |
| weekly | Every Sunday | weekly-2026-03-15 |
| monthly | Every 1st of the month | monthly-2026-04-01 |
The Complete Backup Script
IMPORTANT !
Note that rsync does not work with the number of backups. To avoid having to configure two values, the setting is interpreted as “older than xx days.” Files older than this threshold are deleted, depending on the backup schedule (daily, weekly, monthly).
# Aufbewahrungsdauer (in Tagen)
KEEP_DAILY=7 # Keep daily backups that are less than 7 days old
KEEP_WEEKLY=30 # Keep weekly backups that are less than 30 days old
KEEP_MONTHLY=365 # Keep monthly backups that are less than 365 days old
#!/bin/bash
# =====================================================
# IMMICH POWER-BACKUP mit rsync (Hardlink-Modus)
# =====================================================
# Autor | KSC, Michael Kissner
# Hiomepage | www.klissner.uk
# Version | 2.0
#
# 🇬🇧 Link for Support / donation for the channel:
# 🇩🇪 Link zur Unterstützung / Spende für denn Kanal:
# PayPal: https://www.paypal.com/donate?hosted_button#MEAN_id=G8CZWPDCM3SNW
# Bank, Bitcoin und Lightning: https://www.ksc-llp.uk/donateyoutube
# =====================================================
#!/bin/bash
# =====================================================
# IMMICH POWER-BACKUP mit rsync (Hardlink-Modus) - KORRIGIERT
# =====================================================
# ---------- KONFIGURATION ----------
SOURCE="/mnt/d/Visual Edit/immich/medien/"
DEST_BASE="/mnt/d/backups/immich/"
LOG="$DEST_BASE/backup.log"
# Aufbewahrungsdauer (in Tagen)
KEEP_DAILY=3 # Behalte tägliche Backups, die jünger als 7 Tage sind
KEEP_WEEKLY=14 # Behalte wöchentliche Backups, die jünger als 30 Tage sind
KEEP_MONTHLY=60 # Behalte monatliche Backups, die jünger als 365 Tage sind
# PostgreSQL Container Name
DB_CONTAINER="immich_postgres"
DB_USER="postgres"
DB_NAME="immich"
# ---------- DATUM ERMITTELN ----------
DATE=$(date +%Y-%m-%d)
YEAR=$(date +%Y)
MONTH=$(date +%m)
DAY=$(date +%d)
DOW=$(date +%u) # 1=Montag, 7=Sonntag
# ---------- BACKUP-TYP BESTIMMEN ----------
# Monatlich? (Am ersten Tag des Monats)
if [ "$DAY" == "01" ]; then
BACKUP_TYPE="monthly"
BACKUP_NAME="monthly-$YEAR-$MONTH"
# Wöchentlich? (Sonntag = 7)
elif [ "$DOW" == "7" ]; then
BACKUP_TYPE="weekly"
BACKUP_NAME="weekly-$YEAR-W$((($(date +%V))))" # Kalenderwoche
# Täglich (alle anderen Tage)
else
BACKUP_TYPE="daily"
BACKUP_NAME="daily-$DATE"
fi
# ---------- PFADE FESTLEGEN ----------
DEST="$DEST_BASE/$BACKUP_NAME"
LATEST_LINK="$DEST_BASE/latest"
DB_BACKUP_DIR="$DEST/database"
DB_BACKUP_FILE="$DB_BACKUP_DIR/immich-db-$DATE.sql"
# ---------- LOGGING-FUNKTION ----------
log() {
echo "$(date '+%Y-%m-%d %H:%M:%S') - $1"
echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" >> "$LOG"
}
# ========== HAUPTPROGRAMM ==========
# Log-Verzeichnis erstellen
mkdir -p "$DEST_BASE"
log "========================================="
log "BACKUP GESTARTET - Typ: $BACKUP_TYPE"
log "========================================="
# Prüfungen
if [ ! -d "$SOURCE" ]; then
log "FEHLER: Quelle $SOURCE existiert nicht!"
log "Aktuelles Verzeichnis: $(pwd)"
exit 1
fi
# Prüfe ob Docker-Container läuft
if ! docker ps | grep -q "$DB_CONTAINER"; then
log "WARNUNG: PostgreSQL Container $DB_CONTAINER läuft nicht - Datenbank-Backup wird übersprungen"
DB_AVAILABLE=false
else
DB_AVAILABLE=true
fi
# Zielordner erstellen
mkdir -p "$DEST"
mkdir -p "$DB_BACKUP_DIR"
log "Zielordner: $DEST"
# ========== DATENBANK-BACKUP ==========
if [ "$DB_AVAILABLE" = true ]; then
log "Starte Datenbank-Backup..."
# pg_dumpall erstellt ein komplettes Backup aller Datenbanken und Rollen
docker exec "$DB_CONTAINER" pg_dumpall -U "$DB_USER" > "$DB_BACKUP_FILE" 2>> "$LOG"
if [ $? -eq 0 ]; then
# Komprimieren um Platz zu sparen
gzip -f "$DB_BACKUP_FILE"
log "✅ Datenbank-Backup erfolgreich: $DB_BACKUP_FILE.gz"
# Größe anzeigen
DB_SIZE=$(du -h "$DB_BACKUP_FILE.gz" | cut -f1)
log " Größe: $DB_SIZE"
else
log "❌ FEHLER: Datenbank-Backup fehlgeschlagen!"
fi
else
log "⚠️ Datenbank-Backup übersprungen (Container nicht verfügbar)"
fi
# ========== FOTO-BACKUP MIT HARDLINKS ==========
log "Starte rsync für Fotos (Hardlink-Modus)..."
# WICHTIG: Quellpfad mit Slash am Ende für korrekte Inhaltskopie
SOURCE_WITH_SLASH="${SOURCE%/}/"
if [ -d "$LATEST_LINK" ] && [ -d "$LATEST_LINK/medien" ]; then
# Hardlinks zum letzten Backup verwenden
rsync -a --delete \
--link-dest="$LATEST_LINK/medien" \
"$SOURCE_WITH_SLASH" \
"$DEST/medien/" >> "$LOG" 2>&1
else
# Erstes Backup
rsync -a "$SOURCE_WITH_SLASH" "$DEST/medien/" >> "$LOG" 2>&1
fi
if [ $? -eq 0 ]; then
log "✅ rsync erfolgreich abgeschlossen"
# Latest-Link aktualisieren
rm -f "$LATEST_LINK"
ln -s "$DEST" "$LATEST_LINK"
log "✅ Latest-Link aktualisiert"
# Größe des Foto-Backups ermitteln
if [ -d "$DEST/medien" ]; then
PHOTO_SIZE=$(du -sh "$DEST/medien" | cut -f1)
log " Größe Fotos: $PHOTO_SIZE"
fi
else
log "❌ FEHLER: rsync fehlgeschlagen!"
exit 1
fi
# ========== AUFRÄUMEN ALTER BACKUPS ==========
log "Starte Aufräumen alter Backups..."
# Sicherstellen, dass wir im richtigen Verzeichnis sind
if ! cd "$DEST_BASE"; then
log "FEHLER: Kann nicht nach $DEST_BASE wechseln!"
exit 1
fi
# Aufräumen basierend auf Backup-Typ und Alter - KORRIGIERT
# Lösche tägliche Backups, die älter als KEEP_DAILY sind
find . -maxdepth 1 -type d -name "daily-*" -mtime +$KEEP_DAILY 2>/dev/null | while read dir; do
log "Lösche altes tägliches Backup: $dir"
rm -rf "$dir"
done
# Lösche wöchentliche Backups, die älter als KEEP_WEEKLY sind
find . -maxdepth 1 -type d -name "weekly-*" -mtime +$KEEP_WEEKLY 2>/dev/null | while read dir; do
log "Lösche altes wöchentliches Backup: $dir"
rm -rf "$dir"
done
# Lösche monatliche Backups, die älter als KEEP_MONTHLY sind
find . -maxdepth 1 -type d -name "monthly-*" -mtime +$KEEP_MONTHLY 2>/dev/null | while read dir; do
log "Lösche altes monatliches Backup: $dir"
rm -rf "$dir"
done
# ========== ZUSAMMENFASSUNG ==========
log "========================================="
log "BACKUP ABGESCHLOSSEN"
log "Typ: $BACKUP_TYPE"
log "Ziel: $DEST"
log " - Datenbank: $([ "$DB_AVAILABLE" = true ] && echo "✅" || echo "⚠️ übersprungen")"
log " - Fotos: ✅ (Hardlinks aktiv)"
log "Aufbewahrung:"
log " - Täglich: $KEEP_DAILY Tage"
log " - Wöchentlich: $KEEP_WEEKLY Tage"
log " - Monatlich: $KEEP_MONTHLY Tage"
log "========================================="
Hinweis: Vergesse nicht die Datei backup_immich.sh als “ausführbar” zu berechtigen:
chmod +x backup_immich.sh
Otherwise, the script cannot be started!
Crontab Setup (Linux/WSL)
Important: Windows Task Scheduler doesn’t work reliably here because WSL instances have issues in background mode. The solution is Cron within your WSL environment:
# Open crontab
crontab -e
# Add the following line (Wed+Sun at 11:00 PM):
0 23 * * 0,3 /mnt/d/Visual\ Edit/immich/backup_immich.sh >> /home/mike/cron.log 2>&1
# Save and exit
Crontab Explanation:
| Position | Value | Meaning |
|---|---|---|
| Minute | 0 | At the full hour |
| Hour | 23 | At 11:00 PM |
| Day | * | Every day |
| Month | * | Every month |
| Weekday | 0,3 | Sunday (0) and Wednesday (3) |
Testing Cron:
# Check if Cron is running
sudo service cron status
# Display crontab
crontab -l
# Check logs
cat /home/mike/cron.log
Summary
Your backup system now:
- ✅ Complete (database + photos)
- ✅ Automatic (Wed+Sun at 11:00 PM)
- ✅ Space-efficient (hardlinks)
- ✅ Versioned (daily/weekly/monthly)
- ✅ Restorable (every snapshot)
Enjoy the peace of mind – your photos are now truly protected!

Support / Donation Link for the Channel
If my posts have been helpful or supported you in any way, I’d truly appreciate your support 🙏
#Immich #PhotoManagement #Photos #PhotoAlbum #Docker #Linux #WSL