How to back up and restore Immich
An Immich backup has two parts, taken in this order: the PostgreSQL database, which Immich already dumps every night at 2:00 into UPLOAD_LOCATION/backups, and the files in UPLOAD_LOCATION, where library, upload and profile hold the originals. Immich copies none of it off the server for you. To restore, put the files back on a fresh install of the same Immich version, then load the database from the welcome screen's Restore from backup or with psql before the server first starts.
What Immich stores and where
Immich runs under Docker Compose. Two settings in the .env file next to docker-compose.yml decide where data lives on the host. UPLOAD_LOCATION (default ./library) is mounted at /data in the server container; DB_DATA_LOCATION (default ./postgres) holds PostgreSQL's own files. This guide assumes the compose files are in /opt/immich-app, so uploads are in /opt/immich-app/library. Print your values:
grep -E '^(UPLOAD_LOCATION|DB_DATA_LOCATION|DB_USERNAME|DB_DATABASE_NAME|IMMICH_VERSION)=' /opt/immich-app/.env| Path | Back up? | What it is |
|---|---|---|
upload/ | Yes | Originals uploaded from the web, the apps and the CLI, by user ID. This is where originals go by default. |
library/ | Yes | Originals, once the storage template has been turned on. With the default UPLOAD_LOCATION=./library, its full path is library/library. |
profile/ | Yes | Profile pictures. |
backups/ | Yes | Immich's own database dumps. |
thumbs/ | Optional | Thumbnails, previews and face thumbnails. A job rebuilds them. |
encoded-video/ | Optional | Videos re-encoded for playback. The originals stay, so these can be rebuilt. |
DB_DATA_LOCATION | No, dump it | PostgreSQL's live data files. PostgreSQL's docs say a file copy is only usable with the server shut down. |
.env, docker-compose.yml | Yes | Paths, the database password and the Immich version. |
Two more places. External libraries are folders you mount into the container; Immich only reads them and they are not under UPLOAD_LOCATION, so back them up where they live. If you moved a folder such as profile/ to another disk, back up that path instead.
Never edit, move or delete files in these folders yourself. The database tracks each file, and Immich reports changed files as missing or as checksum mismatches.
Immich's own database dumps
Immich keeps file paths and all metadata in PostgreSQL and does not scan its folders to rebuild them, so the database matters as much as the photos. It dumps the database on a schedule, by default every day at 2:00, keeping the last 14. Change both under Administration > Settings > Database Dump Settings. To make one now, open Administration > Job Queues, click Create job, pick Create Database Dump and confirm.
Each dump lands in UPLOAD_LOCATION/backups with a name like immich-db-backup-20250729T114018-v1.136.0-pg14.17.sql.gz: the time, then the Immich and PostgreSQL versions. Immich makes it with pg_dump --clean --if-exists and compresses it with gzip --rsyncable. On a running install, Administration > Maintenance > Restore database backup rolls the database back to any dump in that folder.
The dumps hold metadata only, no photos, and they sit on the same disk as everything else. They are part of a backup, not a backup: copy them off the server together with the files.
Dump the database yourself
To dump at a moment you choose, such as right before a file backup, use the command from Immich's docs. Change immich and postgres if you changed DB_DATABASE_NAME or DB_USERNAME:
docker exec immich_postgres pg_dump --clean --if-exists --dbname=immich --username=postgres | gzip --rsyncable > /var/backups/immich-db.sql.gzimmich_postgresis the database container in Immich's compose file. pg_dump runs inside it, so client and server versions always match.--clean --if-existsaddsDROP ... IF EXISTSstatements, so the dump replaces whatever is in the target database.--rsyncablekeeps unchanged parts of the compressed file identical between runs, which helps rsync and deduplicating tools.- Immich's docs add
-t, which allocates a terminal. Docker's docs say that is only useful when a program needs one; leave it out when the output goes to a file.
Immich 2.4 and earlier documented pg_dumpall and a restore into the postgres database. The process changed in 2.5.0; for an older dump, use the restore steps from that version of the docs (the version selector on docs.immich.app).
Back up in the right order
Taken at different moments, the database and the files can disagree. Immich's docs give the safe order: database first, files second. Then the worst case is a few photos on disk that the restored database doesn't know, which you can upload again. The other way round, the database can point at files the backup lacks, and those photos come back broken.
The nightly dump gives you that order for free: any file backup that runs after 2:00 copies backups/ along with photos that are newer than the dump.
For a perfectly matched pair, run docker stop immich_server before the backup and docker start immich_server after it. The web app and uploads are offline in between.
Immich recommends backing up all of UPLOAD_LOCATION. Skipping thumbs/ and encoded-video/ saves space and upload time; after a restore you then rerun the thumbnail and transcoding jobs.
Automate it with restic
A photo library is large and rarely changes, which suits a deduplicating tool: after the first run, each backup uploads only new photos. This script uses restic, set up as in that guide with the repository and password in /etc/restic/env. It streams a fresh dump into restic, backs up the files, then applies retention:
#!/usr/bin/env bash
set -euo pipefail
PATH=/usr/local/bin:/usr/bin:/bin
set -a; . /etc/restic/env; set +a
APP=/opt/immich-app
UPLOAD="$APP/library"
restic backup --tag immich-db --stdin-filename immich-db.sql \
--stdin-from-command -- \
docker exec immich_postgres pg_dump --clean --if-exists --dbname=immich --username=postgres
restic backup --tag immich-files \
--exclude "$UPLOAD/thumbs" --exclude "$UPLOAD/encoded-video" \
"$APP/.env" "$APP/docker-compose.yml" "$UPLOAD"
restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 12 --prunechmod 700 /usr/local/bin/immich-backup.sh30 3 * * * root /usr/local/bin/immich-backup.sh >> /var/log/immich-backup.log 2>&1PATHis set because cron's default leaves out/usr/local/bin, where the restic guide installs restic.--stdin-from-commandruns the dump and stores its output asimmich-db.sql. If pg_dump fails, restic cancels the backup and creates no snapshot. The dump stays uncompressed on purpose: Immich's backup guide notes that compression hurts deduplication, and restic compresses its repository anyway.- The two
--excludeoptions skip the folders Immich can rebuild. Delete them to keep everything. They also skip the hidden.immichmarker files in those folders, which matters for the command-line restore below. forgetkeeps 7 daily, 4 weekly and 12 monthly snapshots. It groups snapshots by host and paths, so the database and the files each keep their own set.- 3:30 is after Immich's 2:00 dump, so each file snapshot also carries a ready-made dump in
backups/.
Large libraries and the 3-2-1 rule
If you clear photos off your phone once they upload, the server holds the only copy. Follow the 3-2-1 rule: three copies, on two kinds of storage, one of them off-site. A second disk or NAS at home plus a bucket in another provider's account gets you there.
Plan the first upload. 500 GB over a 100 Mbit/s uplink is 500 × 8 = 4,000 gigabits, or 40,000 seconds: about 11 hours, longer on a shared line. After that, a day with 2 GB of new photos and videos takes under 3 minutes at the same speed.
- An rsync copy to a second disk restores fast, but with
--deleteit is a mirror: a photo deleted in Immich vanishes from the copy on the next run. Pair it with a versioned tool such as restic or Borg, which Immich's own backup template uses. - Immich keeps deleted assets in its trash for 30 days by default. Monthly snapshots cover mistakes you find after that.
- Checking every byte means downloading the whole repository. The command below reads a random 5% of it, 25 GB of a 500 GB repository, so run it monthly if your storage charges for downloads.
restic check --read-data-subset=5%Restore onto a new server
Immich restores onto a fresh install whose files are already in place. Run the version that made the backup: Immich does not support downgrades, and the dump's name shows its version. On the new server, install Docker and restic, put back /etc/restic/env from the copy you keep off the server, load it and bring back the compose files and the upload folder:
set -a; . /etc/restic/env; set +arestic restore latest --tag immich-files --target /In .env, set IMMICH_VERSION to the backup's exact version, such as v3.2.4. The default v3 follows the newest 3.x release. Then choose one of the two methods from Immich's docs.
From the welcome screen (Immich's recommendation). In /opt/immich-app, start the stack with docker compose up -d and open the web app. Instead of creating an admin account, click Restore from backup. Immich enters maintenance mode and lists each storage folder with its file count; check library, upload and profile, click Next, pick a dump and click Restore. The list shows the dumps in backups/. To use the restic copy, write it there first:
restic dump --tag immich-db latest immich-db.sql | gzip > /opt/immich-app/library/backups/immich-db-restic.sql.gzImmich saves a restore point of the current database, loads the dump, runs any migrations and checks the result. If that fails, it rolls back.
From the command line. This needs a database the server has never started on. If you skipped thumbs/ and encoded-video/, create them with their marker files first, or the server stops at startup with Failed to read:
mkdir -p /opt/immich-app/library/thumbs /opt/immich-app/library/encoded-videotouch /opt/immich-app/library/thumbs/.immich /opt/immich-app/library/encoded-video/.immichThen, in /opt/immich-app, create the containers and start only PostgreSQL:
docker compose createdocker start immich_postgresWait about ten seconds for it to start, then load the dump in one transaction. This uses the restic copy written by the restic dump command above; any .sql.gz dump in backups/ works the same way:
gunzip --stdout /opt/immich-app/library/backups/immich-db-restic.sql.gz | sed "s/SELECT pg_catalog.set_config('search_path', '', false);/SELECT pg_catalog.set_config('search_path', 'public, pg_catalog', true);/g" | docker exec -i immich_postgres psql --dbname=immich --username=postgres --single-transaction --set ON_ERROR_STOP=ondocker compose up -d--single-transactionwithON_ERROR_STOP=onmakes the restore all or nothing: one error rolls everything back.- The
sedstep is copied from Immich's docs. It replaces the empty search path the dump sets withpublic, pg_catalog. - The restore uses the
databaseservice from your restored compose file, which runs Immich's own PostgreSQL image. Don't swap in a stock PostgreSQL image.
If you skipped the generated folders, open Administration > Job Queues and run Generate Thumbnails and Transcode videos for missing assets.
Test the restore
Test on a throwaway server, not beside production: Immich's compose file uses fixed container names, so a second copy on the same host clashes. Restore as above, then count assets on both servers. The numbers should match, give or take what changed since the dump:
docker exec immich_postgres psql --username=postgres --dbname=immich -c 'SELECT count(*) FROM "asset" WHERE "deletedAt" IS NULL;'- On Administration > Maintenance, run Check for missing files. Any result means the database points at files the backup lacks.
- Open a few old photos, download an original and play a video.
- Time the whole run. That is your real recovery time.
Then delete the test server. Testing restores turns this into a routine.
Common errors
| Error | Fix |
|---|---|
Failed to read: "<UPLOAD_LOCATION>/thumbs/.immich ... at startup | A folder the restored database expects is missing. Restore it, or create it with an empty .immich file. |
relation ... already exists during the psql restore | The server started on that database first. Stop the stack, empty DB_DATA_LOCATION and repeat. |
| This backup was created with a different version of Immich! | Install the version in the dump's name, restore, then upgrade. |
Invalid backup name! when uploading a dump | Use only letters, digits, -, _ and . in the name, ending in .sql or .sql.gz. |
| Blank thumbnails, videos that won't play | thumbs/ or encoded-video/ wasn't restored. Run the jobs for missing assets. |
| Photos listed but won't open | The database is newer than the files. Restore files backed up after that dump. |
Frequently asked questions
- Does Immich back up my photos automatically?
- No. It only dumps the database, every night into UPLOAD_LOCATION/backups on the same disk. Copying the photos and those dumps off the server is up to you.
- Which Immich folders do I need to back up?
- upload, library and profile hold the originals, and backups holds the database dumps. thumbs and encoded-video can be rebuilt, though Immich recommends backing up all of UPLOAD_LOCATION.
- Can I back up the postgres folder instead of dumping the database?
- Only with the database stopped. A copy of PostgreSQL's files taken while it runs may not be usable. Use the dumps.
- Can I restore an Immich backup onto a newer version?
- The restore tries to run the migrations itself, but Immich advises a matching version and does not support downgrades. Restore on the dump's version, then upgrade.
How this was checked
Commands, limits and prices were checked against these official pages, on October 4, 2026:
- Immich docs: Backup and Restore
- Immich docs: Backup Script (Borg template)
- Immich docs: System Integrity
- Immich docs: Server Commands
- Immich docs: Docker Compose install
- Immich docs: Upgrading (versioning policy)
- Immich docs: Environment Variables
- Immich docs: System Settings (trash)
- Immich docs: Database Queries
- Immich docs: External Libraries
- Immich v3.2.4 docker-compose.yml
- Immich v3.2.4 example.env
- Immich source v3.2.4: database-backup.service.ts
- Immich source v3.2.4: storage.service.ts (mount checks)
- restic documentation: Backing up
- restic documentation: Restoring from backup
- restic documentation: Removing backup snapshots
- restic documentation: Tuning parameters (compression)
- Docker documentation: docker container run (--tty)
- PostgreSQL documentation: File System Level Backup
- PostgreSQL documentation: pg_dump