
Every sysadmin eventually gets the same request from a friend or family member: “Can you set up something so I stop paying for cloud photo storage?” For years, the honest answer was “not really, unless you want to babysit a half-broken PHP app.” That changed once Immich matured into a genuinely production-ready alternative to Google Photos, and with Ubuntu 26.04 LTS “Resolute Raccoon” now shipping kernel 7.0, Docker 29, and PostgreSQL 18 out of the box, the underlying platform finally caught up to what a modern self-hosted photo server actually needs.
This guide walks through installing Immich on a fresh Ubuntu 26.04 LTS server, not a toy VM with two vCPUs and a prayer, but a configuration that will actually hold up when someone dumps 40,000 photos and four years of iPhone video into it overnight. That scenario is not hypothetical. It happens constantly during the first migration week, and it’s exactly when undersized deployments start throwing OOM errors or grinding the machine-learning worker to a halt.
Immich handles facial recognition, semantic search, live photos, RAW formats, and mobile auto-backup, which means it’s doing considerably more work than a static file server. It runs as a stack of containers: a Postgres database with the pgvecto.rs extension for vector search, Redis for job queuing, a machine-learning microservice, and the main API/server process, all sitting behind whatever reverse proxy you choose to front it with. Getting this right on Ubuntu 26.04 LTS involves a few platform-specific quirks worth knowing before you start, particularly around the switch to sudo-rs and the tightened default firewall behavior in this release.
By the end of this walkthrough, you’ll have a working Immich instance reachable over HTTPS, backed by systemd-managed Docker Compose, with sane resource limits, automatic updates disabled (on purpose, more on that later), and a firewall configuration that doesn’t leave your database port hanging open to the internet. We’ll also cover hardware transcoding, because nobody wants their CPU pegged at 100% every time a phone uploads a 4K video.
Why Ubuntu 26.04 LTS Is a Solid Base for Immich
Ubuntu 26.04 LTS brings Linux kernel 7.0, which matters more than it might seem for this particular workload. Immich’s hardware transcoding features, especially VAAPI and Intel Quick Sync paths, depend heavily on kernel-level driver support, and older kernels shipped with Ubuntu 22.04 caused real headaches with 11th-gen Intel CPUs needing Low-Power mode workarounds. That entire class of problem is largely gone on 26.04.
The release also ships Docker 29 in the default repositories territory, though most admins will still prefer installing directly from Docker’s own APT repo to stay current with security patches faster than Canonical’s cadence allows. PostgreSQL 18 is available system-wide too, but don’t be tempted to use it directly. Immich needs the pgvecto.rs or pgvector extension bundled into its own container image, and mixing that with a host-level Postgres install is a common mistake that leads to version mismatch errors nobody enjoys debugging at 11 PM.
One more thing worth flagging: 26.04 defaults to sudo-rs instead of the traditional sudo binary. Functionally it behaves the same for almost everything in this guide, but if you’re scripting automated deployments, double-check any sudo flag usage against the sudo-rs manpage, since a handful of edge-case flags aren’t yet at parity.
Prerequisites and Server Sizing
Before touching a terminal, get the sizing right. Immich’s official minimums are 2 CPU cores and 6 GB of RAM, but that’s genuinely a bare-minimum figure for a small personal library. In practice, for a household or small team library in the tens of thousands of photos:
- CPU: 4 cores minimum, 8 cores/16 threads if you expect concurrent video transcoding and machine-learning jobs running simultaneously.
- RAM: 8 GB minimum, 16 GB comfortable, 32 GB if multiple users upload simultaneously or you’re running facial recognition against a large backlog.
- Storage: A fast NVMe SSD for the Postgres database and Redis, and separate storage (can be an HDD array, NAS mount, or cloud-backed volume) for the actual photo/video library. Never point the database volume at network storage; it’s explicitly unsupported and will eventually corrupt under load.
- Architecture: amd64 or arm64. Note that since Immich v3, the amd64 machine-learning container requires x86-64-v2 instruction set support, so ancient CPUs (pre-2010 Nehalem-era or earlier) may fail to run the ML service at all.
You also need a domain name if you want proper HTTPS (Let’s Encrypt won’t issue for bare IPs), and root or sudo access on a fresh Ubuntu 26.04 LTS installation.
Step 1: Update the System and Prep the Environment
Start clean. Skipping this step and running into a weird apt dependency conflict three steps later is a rite of passage nobody needs to repeat.
sudo apt update && sudo apt full-upgrade -y
sudo reboot
After the reboot, install a few utilities that make life easier later:
sudo apt install -y curl wget git ufw fail2ban htop
Create a dedicated non-root user if you haven’t already. Running Docker as root indefinitely is a habit worth breaking early:
sudo adduser immichadmin
sudo usermod -aG sudo immichadmin
su - immichadmin
Step 2: Install Docker Engine and Docker Compose
Ubuntu’s default repos include Docker packages now, but the official Docker repository stays ahead on patches and is what most production environments actually use. Add it properly instead of relying on the snap version, which has caused mount and networking oddities for Immich users in the past.
# Remove any old/conflicting packages first
for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do sudo apt remove $pkg; done
sudo apt install -y ca-certificates curl gnupg
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
Add your user to the docker group so you’re not typing sudo before every command:
sudo usermod -aG docker $USER
newgrp docker
Verify everything landed correctly:
docker --version
docker compose version
Note the space between docker and compose. The hyphenated docker-compose binary is deprecated and shouldn’t be used on a fresh 26.04 install; the plugin-based syntax is the only one worth learning at this point.
Step 3: Download and Configure Immich
Create a dedicated directory structure. Keeping this outside your home directory under /opt makes backup scripting and permission management considerably cleaner in the long run.
sudo mkdir -p /opt/immich
sudo chown $USER:$USER /opt/immich
mkdir -p /opt/immich/library
cd /opt/immich
Pull the current release’s Compose file and environment template directly from the project’s GitHub releases:
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 the .env file. This is where most people rush and where most mistakes originate:
nano .env
Key values to set carefully:
UPLOAD_LOCATION=/opt/immich/library
DB_DATA_LOCATION=/opt/immich/postgres
DB_PASSWORD=<generate-a-strong-random-password>
IMMICH_VERSION=release
TZ=Asia/Jakarta
That UPLOAD_LOCATION should point somewhere with genuine capacity, ideally on a separate mount from your OS disk. If you’re mounting an external drive or a NAS share for the actual photo library (not the database, that stays local), verify it’s mounted persistently in /etc/fstab before proceeding, or you’ll get a nasty surprise the first time the server reboots and Immich starts writing to an empty directory instead of your mount point.
Generate a strong database password rather than leaving the placeholder:
openssl rand -base64 24
Paste that into DB_PASSWORD. Never leave default credentials on anything internet-facing, even behind a firewall. Defense in depth isn’t optional anymore, it’s table stakes.
Step 4: Launch the Stack
With the environment file squared away, bring the containers up:
docker compose up -d
Watch the logs on first boot, since the database migrations and initial container pulls take a minute or two:
docker compose logs -f
Once the immich-server container reports it’s listening, open a browser and navigate to:
http://your-server-ip:2283
You’ll land on the admin account creation screen. Set a strong password here too; this account has full administrative control over the entire library and user base.

Step 5: Reverse Proxy and HTTPS with Nginx
Exposing port 2283 directly to the internet is asking for trouble. Put Nginx in front of it and terminate TLS properly.
sudo apt install -y nginx certbot python3-certbot-nginx
Create a server block:
sudo nano /etc/nginx/sites-available/immich
server {
listen 80;
server_name photos.yourdomain.com;
client_max_body_size 50G;
proxy_read_timeout 600s;
proxy_send_timeout 600s;
location / {
proxy_pass http://127.0.0.1:2283;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
That client_max_body_size 50G isn’t excessive paranoia. Immich supports large video uploads, and the default Nginx body size limit will silently reject large mobile backups with a 413 error that’s genuinely confusing to diagnose the first time you see it.
Enable the site and obtain a certificate:
sudo ln -s /etc/nginx/sites-available/immich /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
sudo certbot --nginx -d photos.yourdomain.com
Firewall and Security Hardening
Ubuntu 26.04 LTS ships with tighter default firewall assumptions than earlier releases, but you still need to configure UFW explicitly. Docker manages its own iptables rules under the hood, which sometimes conflicts with UFW in confusing ways if not handled deliberately.
sudo ufw allow OpenSSH
sudo ufw allow 'Nginx Full'
sudo ufw enable
sudo ufw status verbose
Crucially, do not open port 2283 or 5432 (Postgres) directly through UFW. Those should only be reachable via localhost through the reverse proxy. Docker’s default bridge network already isolates the Postgres and Redis containers from the outside world unless you’ve explicitly published their ports, which the standard Compose file doesn’t do.
Additional hardening steps worth doing on any internet-facing server:
- Disable password-based SSH login and enforce key-based auth only.
- Configure fail2ban with a jail targeting Nginx auth failures if you’re not using Immich’s own OAuth integration.
- Set
DB_PASSWORDrotation reminders; treat it like any other production credential. - Keep the
.envfile permissions locked down:chmod 600 .env.
Hardware Transcoding: Don’t Skip This
If your server does any real volume of video uploads, software transcoding alone will melt your CPU. Immich supports NVENC (NVIDIA), Quick Sync (Intel), and VAAPI acceleration.
For Intel Quick Sync on a modern CPU, download the hardware acceleration Compose override:
wget -O hwaccel.transcoding.yml https://github.com/immich-app/immich/releases/latest/download/hwaccel.transcoding.yml
Edit docker-compose.yml, find the immich-server service, and uncomment the extends block pointing to this file, setting the backend to qsv or vaapi depending on your hardware. Redeploy:
docker compose up -d
Then, inside the Immich admin panel under Video Transcoding Settings, switch the hardware acceleration dropdown to match. One quirk worth knowing: on certain older Intel chips (Jasper Lake, Elkhart Lake), you also need to set the Constant Quality mode to CQP, or transcoding jobs will fail silently and pile up in the queue without any obvious error in the logs.
Troubleshooting Common Issues
Container stuck restarting on first boot
Almost always a permissions problem on UPLOAD_LOCATION or DB_DATA_LOCATION. Check ownership matches the user Docker is running as:
docker compose logs immich-server | tail -50
sudo chown -R 999:999 /opt/immich/postgres
Machine learning jobs stuck at 0% forever
This is often a memory ceiling issue. The ML container needs headroom beyond what the main server uses; check docker stats during a facial recognition job and bump RAM if it’s maxing out.
413 Request Entity Too Large on mobile upload
Nginx’s client_max_body_size wasn’t set high enough, or you forgot to reload Nginx after editing the config. Confirm with sudo nginx -t before reloading.
Mobile app can’t connect over HTTPS
Usually a certificate chain issue or a firewall rule blocking port 443. Test externally with curl -I https://photos.yourdomain.com from a different network to rule out local DNS caching.
Database won’t start after a host reboot
Check that your mount points in /etc/fstab are correct, especially if DB_DATA_LOCATION sits on a separate disk. A missing mount at boot means Docker creates an empty directory in its place, and Postgres will refuse to start against what looks like a corrupted data directory.
Backup Strategy and Long-Term Maintenance
Photos are the kind of data people notice missing only after it’s gone. Set up automated backups covering both the Postgres database dump and the actual library files.
docker exec -t immich_postgres pg_dumpall -c -U postgres > /opt/backups/immich-db-$(date +%F).sql
Pair this with an rsync or restic job targeting your library directory, ideally to offsite or object storage. Test the restore process at least once; a backup that’s never been restored is a backup you don’t actually have.
For updates, resist the urge to auto-pull latest blindly on a production instance. Pin IMMICH_VERSION to a specific release tag, read the release notes (breaking changes do happen, particularly around database migrations), and update on a schedule you control rather than whatever cron job Watchtower decided to run at 3 AM.
Immich on Ubuntu 26.04 LTS is, frankly, one of the more satisfying self-hosting projects to run well. It rewards a little upfront care in sizing and security, and once it’s running, it just quietly works, which is exactly what you want from infrastructure nobody should have to think about twice a week.