An isometric cyan photo-library stack flowing from a phone into a dark navy server panel — self-hosted Immich replacing cloud photo storage.
← Back
[ self-hosting ]

Self-Host Immich on a VPS: Your Own Private Google Photos

enim · Jun 8, 2026 · 8 min read · Updated: Jun 13, 2026
TL;DR: Immich is an open-source, self-hosted photo and video platform that genuinely replaces Google Photos — phone auto-backup, AI face and object search, albums, timeline, the lot. This guide runs it with Docker on a VPS: plan your storage, bring up the official Compose stack, put it behind SSL with a large upload limit, connect the mobile app, turn on machine-learning search, and — the part most guides skip — back up the database and the library. Immich is heavier than a typical self-hosted app, so storage and RAM planning matter.

Google Photos quietly became one of the stickiest products on the internet. Years of memories, free face grouping, search that actually works — and then the free tier ended, the storage bill started climbing, and every photo you take feeds a model you don't control. The lock-in is the point.

Immich is the escape hatch that finally feels like a real product instead of a compromise. It's an open-source photo and video manager you run yourself, with a polished mobile app that backs up your camera roll automatically, a fast timeline, shared albums, and machine-learning search that finds faces and objects — all on hardware you own, with photos that never leave your server.

In this guide I'll stand up Immich with Docker, put it behind a reverse proxy with HTTPS, wire in the mobile app, and back it up properly. It runs fine on a home-lab box or a VPS — with one caveat I'll be upfront about: Immich is hungrier than the average self-hosted app, so we'll plan for that first.

Why Immich (and an honest resource warning)

What makes Immich worth the effort over a plain file server:

  • Automatic phone backup. The iOS/Android app uploads new photos and videos in the background, exactly like Google Photos. This is the feature that makes self-hosting photos actually stick.
  • AI search that runs locally. A machine-learning container does face recognition and CLIP-based "smart search" — type "beach sunset" or "my dog" and it finds them. None of it phones home.
  • Real library features. Timeline, albums, shared links, favourites, map view, duplicate detection, multi-user support for a household.

The trade-off is honest to state: Immich is resource-hungry. The photos and videos themselves need real disk — a 60 GB Google Photos export needs more than 60 GB once thumbnails and transcodes are generated. The machine-learning container can spike to 2–4 GB of RAM while it indexes faces. A €5 single-core VPS can run Immich for a small library, but a multi-year family archive wants more storage and ideally 4 GB+ of RAM. This is exactly why Immich is a "size it to your library" job rather than a one-click app — more on that at the end.

Prerequisites

  • A server with Docker and Docker Compose. A VPS or a home box both work. New to this? My build-from-scratch VPS guide gets you a working Docker host.
  • A hardened host. Your entire photo history is about to live here — run through Harden Your Linux VPS in 10 Minutes first if you haven't.
  • Enough storage for your library, plus headroom. Estimate your current photo size, then double it for thumbnails, transcodes, and growth.
  • A domain and a reverse proxy for clean HTTPS. I use Nginx Proxy Manager — see NPM vs Traefik vs Caddy if you're choosing.
  • Root or sudo on the server.

Step 1: Plan your storage

Immich keeps two things you must not lose:

  • The library — your actual photos and videos, plus generated thumbnails. This is the big one.
  • A Postgres database — albums, faces, metadata, users. Small, but losing it means re-indexing everything and losing your album structure.

Decide now where the library lives. On a VPS with a small system disk, attach a block-storage volume and point Immich's upload location at it — don't let photos quietly fill your root partition. I'll use /srv/immich/library in this guide; swap in your mount.

sudo mkdir -p /srv/immich/library /srv/immich/postgres

Step 2: Bring up the official Compose stack

Immich ships a maintained Compose file and an example env — always start from those rather than a hand-rolled one, because the component versions (server, ML, Redis, Postgres) have to match.

sudo mkdir -p /opt/immich && cd /opt/immich
wget -O docker-compose.yml https://github.com/immich-app/immich/releases/latest/download/docker-compose.yml
wget -O .env https://github.com/immich-app/immich/releases/latest/download/example.env

Now edit .env. The three lines that matter:

# /opt/immich/.env
UPLOAD_LOCATION=/srv/immich/library
DB_DATA_LOCATION=/srv/immich/postgres
DB_PASSWORD=a-long-random-password-change-this

# Pin a real version — do NOT run 'release'/latest in production.
# Immich moves fast and occasionally ships breaking DB migrations.
IMMICH_VERSION=v1.119.0
Pin the version. Immich is under heavy development and a few releases have required manual migration steps. Pinning IMMICH_VERSION to a specific tag means you choose when to upgrade — after reading the release notes — instead of an unattended docker compose pull surprising you. Check the releases page for the current tag.

Bring it up:

sudo docker compose up -d
sudo docker compose ps      # all containers should be 'running'/'healthy'

Immich listens on port 2283. From the server: curl -I http://localhost:2283 should return an HTTP response.

Step 3: First boot and admin setup

Open http://your-server-ip:2283 (we'll add HTTPS next). The first screen creates the admin account — use a strong, unique password; this account can see every user's library.

Once in, go to Administration → Settings and, importantly, turn off public registration unless you specifically want others to sign up. For a household, create each person their own user under Administration → Users.

Step 4: HTTPS and the large-upload trap

The mobile app needs a real HTTPS endpoint, and you don't want photos travelling over plain HTTP. Point a subdomain (say photos.yourdomain.com) at the server and create a proxy host forwarding to your-server:2283, with a Let's Encrypt certificate.

There's one gotcha that catches everyone: default proxy upload limits are tiny, so a 200 MB video upload fails with a 413 error. In Nginx Proxy Manager, open the proxy host's Advanced tab and add:

client_max_body_size 50000M;
proxy_read_timeout 600s;
proxy_request_buffering off;

That allows large video uploads and stops big transfers timing out. If you use raw Nginx, the same client_max_body_size directive goes in your server block. Reload the proxy and confirm https://photos.yourdomain.com loads with a valid certificate.

Step 5: Connect the mobile app

Install Immich from the App Store or Play Store. On login, enter your server URL (https://photos.yourdomain.com) and your account credentials.

Then the whole point of this exercise:

  1. Open Backup in the app.
  2. Select the albums to back up (or your entire camera roll).
  3. Enable background backup and, on iOS, Background App Refresh for Immich.

New photos now upload automatically. You can keep "foreground backup" on too, so opening the app forces a sync. Once you've confirmed everything is safely on your server, then consider freeing space on your phone — never before you've verified the backup.

Step 6: Turn on AI search and face recognition

The immich-machine-learning container handles smart search and faces. After your library uploads, Immich queues background jobs to index it. Watch them under Administration → Jobs — "Smart Search" and "Face Detection" will churn through your photos.

This is the RAM-hungry phase. On a small VPS, let it run overnight; if the ML container gets OOM-killed (check docker compose logs immich-machine-learning), index in smaller batches or give the box more memory. Once it's done, searching "passport", "whiteboard", or a person's name just works — locally, with nothing sent to a third party.

Step 7: Back it up — both halves

Immich is not a backup. It's the primary copy of irreplaceable data, which means it needs its own backup. Two pieces:

  • The Postgres database — dump it with pg_dump, don't copy the live data directory (you'll catch it mid-write and get a corrupt copy).
  • The library (/srv/immich/library) — your actual photos.

The clean pattern is a nightly database dump plus an off-site, encrypted snapshot of both. I already wrote the exact setup I use — the 3-2-1 backup guide with restic + Backblaze B2 — and Immich slots straight into it: add a docker exec pg_dump line for the database, and point restic at the library directory. Run a restore test before you trust it; an untested backup of your only photo archive is just optimism.

Step 8: Keep it private

A photo server is about as sensitive as a service gets. Two decisions worth making deliberately:

  • Do you need it on the public internet at all? If Immich is only for you and your household, the safest option is to not expose it publicly and reach it over a VPN instead — my WireGuard setup takes ten minutes and removes the entire public attack surface. If you do expose it (so the mobile app works off-Wi-Fi without a VPN), keep public registration off and the admin password strong.
  • Lock down the containers. The host running your photos deserves the same care as any production box — the Docker security best practices post covers the non-root, capability-dropping, and network-isolation basics.

Troubleshooting

Uploads fail with HTTP 413. The reverse proxy's body-size limit is too low. Set client_max_body_size 50000M; (Step 4) and reload the proxy.

The mobile app can't reach the server. Almost always the URL or the certificate. Use the full https:// URL, confirm the cert is valid in a browser first, and check the app isn't on a network that can't resolve your domain.

The machine-learning container keeps restarting. Out of memory during indexing. Check docker compose logs immich-machine-learning. Give the host more RAM, or let indexing run when the box is otherwise idle.

Containers won't start after an upgrade. You probably jumped versions across a breaking migration. Read the release notes for every tag between your old and new IMMICH_VERSION, and restore your database dump if a migration half-applied. This is exactly why we pinned the version in Step 2.

Library is filling the disk. Photos are big. Move UPLOAD_LOCATION to a larger block-storage volume and migrate the existing files there, or prune duplicates from within Immich.

Conclusion

You now have a private, self-hosted photo platform that does the things that kept you on Google Photos — automatic phone backup, face and object search, albums, a clean timeline — on a server you control, with photos that are encrypted in your off-site backups and visible to no one but you. No subscription creeping upward, no model training on your family.

The honest catch is the one I led with: Immich rewards a little planning. Size the storage to your library with headroom, give the machine-learning container enough RAM, pin your version, and — above all — back up both the database and the library and test the restore.

If sizing the storage, wiring the reverse proxy, and getting the upload limits and backups right sounds like more of a weekend than you want to spend, this is exactly the kind of job I quote on my VPS setup service. Immich isn't part of the flat-rate stack precisely because it needs sizing to your library — so describe your library in the order box and I'll come back with a price for a hardened server running Immich, behind SSL, with backups configured, on infrastructure you own. Prefer to keep building yourself? The self-hosted SaaS alternatives roundup is the natural next read.

Try the services

  • Immich — free, open source, self-hosted. The whole platform in this guide. (GitHub)
  • Backblaze B2 — ~$6/TB/month off-site target for your library backups. No affiliate link (not a partner).

Affiliate disclosure

No affiliate links in this post. Immich is free and open source; Backblaze B2 is a tool I pay for and run myself. The $49-base VPS setup (Immich quoted to size) is my own paid service. — enim

enim

Security researcher, CTF player, and compulsive self-hoster. Building byte-guard.net from a $10/mo Hetzner VPS. Everything I publish I have actually run in production.

Comments

Sign in with GitHub to comment. Threads live in the byteguard-comments repo.