HomeTechOps

Self-Hosting

Immich photos won't back up

Fix an Immich phone that won't upload — the foreground/background split, iOS Background App Refresh, Android battery killers, Wi-Fi-only default — plus the DB+library backup that actually restores.

Problem summary

An Immich phone that 'isn't backing up' is almost always a mobile-OS background-task problem, not a server problem: iOS Background App Refresh off, an Android vendor battery-saver killing the worker, the Wi-Fi-only default, or confusing the foreground and background uploaders (they're separate mechanisms that don't hand off). The deeper operator trap is backup itself — Immich stores asset paths in Postgres and does NOT rescan the library to rebuild the DB, so a restore that has the photo files but no matching `pg_dump` leaves you with orphaned files and an empty timeline. Back up both the database and the `library`/`upload`/`profile` folders, kept consistent.

Operator snapshotEvidence first
First proof

Open the app to the foreground and watch a manual backup run.

Screen to open

docker exec -t immich_postgres pg_dump --clean --if-exists --dbname=immich --username=postgres | gzip > /backup/immich-dump.sql.gz

Expected signal

Assets upload while the app is open (foreground path works).

Stop boundary

If files and DB drift apart, the restore will show orphaned assets.

Layer path

1Immich backup has two independent layers: the phone uploading assets, and the server safely storing them. 'Not backing up' is almost always the phone layer — and almost always the mobile OS throttling the background task, not Immich failing.
2Foreground and background backup are separate mechanisms that do not hand off. The foreground uploader runs while the app is open; the background worker only starts once the app is backgrounded, and the OS scheduler ultimately decides when. For a first bulk import, use foreground.
3iOS gates background work behind Background App Refresh; Android vendors kill background workers with battery 'optimization'. The Wi-Fi-only default and an unselected album are the other two everyday causes.
4The server-side trap is backup correctness: Immich keeps asset paths in Postgres and does NOT rescan the library to rebuild the database, so files without a matching DB dump restore as orphaned with an empty timeline.
Runbook

Step-by-step runbook

Start here. Do each check in order, compare it to the expected result, and stop when the evidence explains the failure or the safe stop point applies.

1

Reproduce in the foreground

Check: Open Immich and run a manual/foreground backup.

Expected result: Assets upload while the app is open.

If not: If it fails here, treat it as connectivity/login/version, not background throttling.

2

Fix the OS background path

Check: Enable iOS Background App Refresh or exempt the Android app from battery optimization.

Expected result: The background worker runs after the app is backgrounded.

If not: If the OS still throttles, rely on foreground for large imports.

3

Check network + album scope

Check: Confirm cellular is allowed (if wanted) and the album is selected.

Expected result: The selected albums upload on your current network.

If not: Enable cellular and select the album, then re-enter the backup page to refresh counts.

4

Set up the database dump

Check: Schedule the pg_dump (or the prodrigestivill/postgres-backup-local sidecar).

Expected result: A fresh, non-empty dump exists alongside the library backup.

If not: Fix container name / DB creds if the dump errors or is empty.

5

Back up the originals consistently

Check: Back up library/, upload/, profile/ in the same window as the DB dump.

Expected result: The DB dump and the asset folders are from a consistent point in time.

If not: Quiesce or snapshot if assets change during the copy.

Safe stop: If files and DB drift apart, the restore will show orphaned assets.

6

Prove the restore

Check: Restore the DB + originals onto a scratch stack and open the timeline.

Expected result: Assets appear with a populated timeline, not orphaned files.

If not: If the timeline is empty, the DB and library weren't a matching pair — restore a consistent set.

Decision tree

Decision tree

If: Foreground upload also fails

Then: Not a background-throttle problem — it's connectivity, login, or version.

Action: Check the server URL/TLS, re-login, and match app/server versions before touching OS settings.

If: Foreground works, background doesn't (iOS)

Then: iOS is not running the background task.

Action: Enable Background App Refresh; for reliable bulk imports use foreground — iOS background timing is not guaranteed.

If: Foreground works, background doesn't (Android)

Then: A vendor battery policy is killing the worker.

Action: Exempt Immich from battery optimization and any 'deep sleep'/app-kill list, then retest.

If: Uploads stop off-Wi-Fi

Then: The Wi-Fi-only default is in effect.

Action: Allow cellular in backup settings if that's intended.

If: Assets uploaded but missing after a restore

Then: DB/file-set mismatch — files exist without matching database rows.

Action: Restore the matching pg_dump; never restore the library without its database.

Safe stop: Stop and restore from a consistent DB+library pair rather than re-importing blindly.

Evidence

Evidence table

SymptomEvidence to collectLikely layerNext action
Backup page 'remaining' count never decreases in the backgroundWhether foreground upload moves the same countMobile OS background-task schedulingFix Background App Refresh (iOS) / battery exemption (Android); use foreground for bulk.
Uploads work at home, stop on mobile dataThe network type when it stallsWi-Fi-only backup settingEnable cellular uploads in settings if intended.
App can't log in or sync after an updateApp version vs server versionVersion skew (major+minor)Match app and server on the same v2.x line.
Restored Immich shows files on disk but an empty timelineWhether a matching pg_dump was restored with the libraryDatabase/library inconsistencyRestore the consistent DB dump; the library alone won't rebuild the timeline.
Postgres won't start / dump errors after a power loss or SD-card hostPostgres logs; storage medium under DB_DATA_LOCATIONPostgres corruption (network share / SD card / unclean shutdown)Per Immich's FAQ, force a dump with zero_damaged_pages=on, then restore a pre-corruption backup.
Reference

Commands and settings paths

Back up the Immich database

docker exec -t immich_postgres pg_dump --clean --if-exists --dbname=immich --username=postgres | gzip > /backup/immich-dump.sql.gz

Where: On the Docker host running the Immich stack

Expected: A non-empty gzipped SQL dump is written.

Failure means: An error or near-empty file means the container name/DB creds are wrong, or the DB is unhealthy.

Safe next step: Confirm DB_DATABASE_NAME/DB_USERNAME (default immich/postgres) and that immich_postgres is healthy.

Confirm which asset folders to back up

echo $UPLOAD_LOCATION # back up its library/, upload/, profile/ subfolders

Where: On the Docker host (read your .env)

Expected: UPLOAD_LOCATION resolves to your data path; library/upload/profile hold the originals.

Failure means: If empty, the variable isn't set in .env and the default path is in use.

Safe next step: Back up library/, upload/, profile/ only; skip regenerable thumbs/encoded-video.

Restore the database into a clean stack

gunzip < /backup/immich-dump.sql.gz | docker exec -i immich_postgres psql --dbname=immich --username=postgres --single-transaction --set ON_ERROR_STOP=on

Where: On the new host after docker compose create + starting immich_postgres

Expected: psql applies the dump with no ON_ERROR_STOP abort.

Failure means: An 'earth type does not exist' error is the known reverse-geocoding restore bug.

Safe next step: Apply Immich's documented sed fix to the search_path line before piping into psql.

Hardware boundary

Hardware and platform boundary

Change only when

  • Add a scheduled DB-dump sidecar once you have more than a trivial library — manual dumps get skipped.
  • Move DB_DATA_LOCATION onto reliable local storage (not an SD card or network share) if you've seen Postgres corruption.

Evidence that matters

  • A consistent DB-dump + library/upload/profile backup pair.
  • Reliable local storage for Postgres and a UPS-clean shutdown.
  • Matching app/server versions on the same v2.x line.

Evidence that does not matter

  • Backing up thumbnails or encoded video — they regenerate.
  • Chasing a faster phone uploader when the real fix is an OS background-task setting.

Avoid

  • Restoring the library without its matching database dump.
  • Relying on experimental self-signed-cert/Basic-Auth connection features for upload — they often break it.

Related tool

Use the linked tool to turn this runbook into a guided check for your exact setup.

Backup plan builder

Related problems

Last reviewed

2026-06-03 · Reviewed by HomeTechOps. Built from 2026-06 research verified against Immich's backup-and-restore docs, the backup FAQ, and the stable-release notes. The operator differentiator is separating the (common) mobile-OS background-task cause from the (dangerous) DB/library backup-consistency cause, and insisting the restore is proven on a scratch stack rather than assumed.

Sources/assumptions

  • Assumes the official Immich Docker Compose stack (immich-server, machine-learning, Redis, and the custom ghcr.io/immich-app/postgres image) with the mobile app installed.
  • Immich reached stable v2.0.0 (2025-10-09) and uses semantic versioning; the current line is v2.7.x as of 2026-06. v3.0.0 was in release-candidate state, not confirmed final — verify the GitHub releases page before assuming it shipped.
  • Backup commands are stated from Immich's own backup-and-restore docs (pg_dump of the immich DB + the original-asset folders); thumbnails/encoded video are regenerable and not backed up.

Source-backed checks

HomeTechOps turns official docs and conservative safety rules into a shorter runbook. These links are the source trail for the page direction.