Immich zu Hause zu betreiben ist immer ein Kompromiss zwischen Sicherheit, Komfort und Zuverlässigkeit beim Hochladen. In der Praxis gibt es drei gängige Möglichkeiten, den Server freizugeben: direkter Zugriff, VPN-Zugriff oder Cloudflare vor einem Reverse-Proxy. Jede Option verändert sowohl die Angriffsfläche als auch die Funktionsweise bei großen Uploads.
Drei Zugriffsmodelle
Direkter Zugriff
Die einfachste Konfiguration ist die reine Portweiterleitung. Das funktioniert zwar, schafft aber auch die größte öffentliche Angriffsfläche. Für ein privates Fotoarchiv ist das in der Regel die riskanteste Option.
VPN-Zugriff
Ein VPN wie WireGuard ist das sicherste Modell. Das Smartphone verbindet sich zuerst mit dem Heimnetzwerk und greift dann auf Immich zu, als wäre es ein interner Dienst. Dadurch bleibt die öffentliche Angriffsfläche klein, da die App nicht wie eine normale öffentliche Website nach außen hin sichtbar ist.
Für einen einzelnen Nutzer oder einen technisch versierten Haushalt ist dies oft die sicherste Option. Der Nachteil ist die Benutzerfreundlichkeit. Jedes Smartphone benötigt einen VPN-Client, der installiert, konfiguriert und aktiv sein muss. Bei Familienmitgliedern bricht das saubere Design oft genau an dieser Stelle zusammen.
Cloudflare davor
Cloudflare ist der praktische Kompromiss. Es bietet dem Dienst einen öffentlichen Zugangspunkt, ohne den Immich-Container direkt dem Internet auszusetzen. In den eigenen Fernzugriffs-Dokumenten von Immich wird erwähnt, dass ein Remote-Reverse-Proxy wie Cloudflare die Sicherheit verbessern kann, indem er die Server-IP verbirgt und gezielte Angriffe erschwert.
Das macht Cloudflare attraktiv, wenn das Ziel ein einfacher Zugriff für die Familie ist, ohne dass auf jedem Gerät ein VPN erforderlich ist. Der Nachteil ist, dass Cloudflare Teil des Anfragepfads wird, und das spielt bei großen Uploads eine Rolle.
Warum Cloudflare der richtige Ausgangspunkt war
Bei einem gemeinsamen Familienfoto-Dienst ist Komfort fast genauso wichtig wie Sicherheit. Eine reine VPN-Konfiguration ist aus Sicherheitssicht sauberer, lässt sich aber bei technisch weniger versierten Nutzern schwerer umsetzen. Cloudflare bietet ein normales App- und URL-Erlebnis, ohne den Immich-Dienst direkt zu veröffentlichen.
Daher ist Cloudflare hier eine gute erste Schutzebene. Es ist nicht sicherer als ein reines VPN-Design, aber deutlich besser als eine direkte Exposition. Und es löst das Problem des Familienzugangs viel besser als WireGuard allein.
Der Haken: große Uploads
Der größte Nachteil zeigt sich bei großen Dateien. Der Cloudflare-Weg wird oft zum limitierenden Faktor für Uploads, und die häufig gemeldete Obergrenze liegt bei etwa 100 MB bei Proxy-Konfigurationen der unteren Preisklasse. Das macht sich bei größeren Videos sehr schnell bemerkbar.
Das eigentliche Problem ist also nicht, dass MP4-Dateien etwas Besonderes sind. Das Problem ist, dass der Cloudflare-Weg zwar bequem und sicherer für den öffentlichen Zugriff ist, aber nicht der beste Weg für große Uploads von vertrauenswürdigen Geräten darstellt.
Die praktische Lösung
Die sinnvolle Lösung ist ein Hybridmodell. Behalte Cloudflare als öffentlichen Zugang für den normalen und familienfreundlichen Zugriff bei, lass aber vertrauenswürdige Geräte über WireGuard und lokales DNS einen direkten Weg zum Heimserver nutzen.
So bleiben der Komfort und die geringere Sicherheitsrisiken von Cloudflare erhalten, während der Upload-Engpass für dein eigenes Smartphone oder andere vertrauenswürdige Clients vermieden wird. Die gleiche Immich-URL funktioniert weiterhin überall. Nur der Netzwerkpfad ändert sich.
So funktioniert die Einrichtung
Der öffentliche Pfad ist mehrschichtig aufgebaut, sodass Immich nicht direkt von Docker aus ins Internet exponiert ist. Der Internetverkehr geht zuerst an Cloudflare, Cloudflare leitet ihn über den benannten Tunnel weiter fyp, der Tunnel sendet Anfragen an Nginx auf localhost:8283, und Nginx leitet den Haupt-Immich-Hostnamen an den Immich-Container auf 127.0.0.1:2283.
Parallel dazu gibt es auch einen direkten lokalen HTTPS-Pfad für vertrauenswürdige Clients. Im Heimnetzwerk lauscht Nginx ebenfalls auf Port 443 mit TLS, sodass LAN-Geräte denselben Immich-Dienst erreichen können, ohne die Cloudflare-Route zu nutzen.
Beispiel für den Datenfluss
Internet
└─► Cloudflare
└─► cloudflared tunnel "fyp"
└─► localhost:8283 (nginx)
└─► http://127.0.0.1:2283 (Immich Docker)
Local network
└─► nginx :443 SSL
└─► http://127.0.0.1:2283 (Immich Docker)
Hostnamen-Struktur
Eine übersichtliche Version dieser Konfiguration kann zwei Hostnamen verwenden:
photos.example-family.netfür die vollständige Immich-Appshare-photos.example-family.netnur für öffentliche Freigabelinks
Diese Trennung ist sinnvoll, da Nginx unterschiedliche Verhaltensweisen anwenden kann, server_name selbst wenn Cloudflare beide über denselben lokalen Listener weiterleitet. Die Haupt-App-Domain stellt die vollständige Benutzeroberfläche bereit. Die Freigabe-Domain kann auf freigabebezogene Pfade beschränkt werden und 403 für alles andere.
Nginx-Design
Der Haupt-Nginx-Block nutzt ein Dual-Listen-Design. Er lauscht auf 127.0.0.1:8283 auf cloudflaredund außerdem auf :443 ssl für den direkten lokalen HTTPS-Zugriff von LAN- und VPN-Clients.
Die Proxy-Konfiguration benötigt die von Immich erwarteten Header: Host, X-Real-IP, X-Forwarded-Proto, und X-Forwarded-For. In einer Cloudflare-basierten Konfiguration real_ip_header CF-Connecting-IP ist wichtig, damit Nginx die ursprüngliche Client-IP wiederherstellen kann, anstatt nur die Tunnelquelle zu sehen. X-Forwarded-Proto https und X-Forwarded-Port 443 hilft Immich dabei, hinter dem Proxy korrekte Weiterleitungs-URLs zu generieren.
Außerdem braucht es WebSocket-Unterstützung, ausreichend große Limits für den Request-Body und Timeouts, die verhindern, dass der Reverse-Proxy beim Hochladen zum ersten Ausfallpunkt wird.
Cloudflare-Tunnel-Rolle
Der Tunnel ordnet die öffentlichen Immich-Hostnamen localhost:8283, und Nginx trennt sie durch server_name. So kann ein Tunnel mehrere interne virtuelle Hosts bedienen, ohne den Anwendungscontainer direkt offenzulegen.
Das ist hier der eigentliche Vorteil der Cloudflare-Ebene. Sie bietet einen einfachen öffentlichen Einstiegspunkt für den privaten Gebrauch und reduziert die direkte Exposition des Heim-Servers. Aber es ist immer noch nicht der ideale Upload-Pfad für vertrauenswürdige Clients und große Dateien.
Lokales DNS und Heimzugang
Um zu vermeiden, dass vertrauenswürdiger Heimverkehr über Cloudflare geleitet wird, nutzt das Heimnetzwerk eine lokale DNS-Überschreibung für den Haupt-Immich-Hostnamen. Die FRITZ!Box unterstützt die Zuweisung eines lokalen DNS-Servers an LAN-Clients im Bereich „Heimnetzwerk → Netzwerk → Netzwerkeinstellungen → IPv4“, sodass Geräte im WLAN den Immich-Hostnamen zur lokalen Server-IP auflösen können, anstatt den öffentlichen Cloudflare-Pfad zu nutzen.
Das bedeutet, dass dieselbe URL sowohl öffentlich als auch privat funktioniert. Außerhalb des Hauses erfolgt die Auflösung über Cloudflare. Innerhalb des Hauses erfolgt die Auflösung direkt zum lokalen Nginx-Endpunkt und nutzt den lokalen HTTPS-Pfad.
WireGuard als Pfad für vertrauenswürdige Geräte
WireGuard erweitert dieses Konzept auf mobile Geräte. Bei diesem Ansatz verbindet sich das Smartphone zuerst mit dem Heimnetzwerk, erhält die privaten DNS-Einstellungen und erreicht denselben Immich-Hostnamen über den direkten Pfad statt über Cloudflare.
Das ist die elegante Lösung für große Uploads. Familiengeräte können weiterhin den einfachen öffentlichen Weg nutzen. Vertrauenswürdige Geräte wie dein eigenes Smartphone können WireGuard und den lokalen DNS-Weg für direkte, zuverlässigere Uploads nutzen.
Sicherheitsbewertung
Dieses Design ist sicherer, als Immich direkt dem Internet auszusetzen. Immich bleibt hinter Docker und Nginx verborgen, und Cloudflare reduziert die direkte Sichtbarkeit der Heim-IP.
Gleichzeitig ist es immer noch nicht sicherer als ein reines VPN-Modell. Eine reine VPN-Konfiguration sorgt für die kleinste öffentliche Angriffsfläche, da die Anwendung selbst überhaupt nicht öffentlich zugänglich ist.
Deshalb funktioniert der hybride Ansatz in der Praxis gut. Cloudflare ist die familienfreundliche „Haustür“. WireGuard und das lokale DNS bieten den besseren Weg für vertrauenswürdige Geräte, besonders wenn es um große Uploads geht.
Nach all der Theorie – hier ist die detaillierte Einrichtung
Schritt 1 – Docker / Immich
Erstelle /home/<user>/immich-app/.env:
UPLOAD_LOCATION=/data/photo_archive/immich_upload
DB_DATA_LOCATION=/home/<user>/immich-app/postgres
IMMICH_VERSION=release
DB_PASSWORD=<strong-random-password>
DB_USERNAME=postgres
DB_DATABASE_NAME=immich
Erstelle docker-compose.yml:
name: immich
services:
immich-server:
container_name: immich_server
image: ghcr.io/immich-app/immich-server:${IMMICH_VERSION:-release}
volumes:
- ${UPLOAD_LOCATION}:/usr/src/app/upload
- /etc/localtime:/etc/localtime:ro
env_file:
- .env
ports:
- '2283:2283'
depends_on:
- redis
- database
restart: always
immich-machine-learning:
container_name: immich_machine_learning
image: ghcr.io/immich-app/immich-machine-learning:${IMMICH_VERSION:-release}
volumes:
- model-cache:/cache
env_file:
- .env
restart: always
redis:
container_name: immich_redis
image: docker.io/redis:6.2-alpine
restart: always
database:
container_name: immich_postgres
image: ghcr.io/immich-app/postgres:14-vectorchord0.3.0-pgvectors0.2.0
environment:
POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_USER: ${DB_USERNAME}
POSTGRES_DB: ${DB_DATABASE_NAME}
POSTGRES_INITDB_ARGS: '--data-checksums'
volumes:
- ${DB_DATA_LOCATION}:/var/lib/postgresql/data
restart: always
volumes:
model-cache:
Immich starten:
docker compose up -d
Immich ist nun auf dem Host unter http://127.0.0.1:2283.
Schritt 2 — Cloudflare-Einrichtung
2a. Voraussetzungen
- Eine in Cloudflare verwaltete Domain (Nameserver verweisen auf Cloudflare)
- Cloudflare Zero Trust aktiviert (die kostenlose Stufe reicht aus)
2b. Erstelle den Tunnel im Cloudflare-Dashboard
- Geh zum Cloudflare-Dashboard → Zero Trust → Netzwerke → Tunnel
- Klicke auf „Tunnel erstellen“ → wähle „Cloudflared“
- Gib dem Tunnel einen Namen (z. B.
my-tunnel) - Befolge die Installationsanweisungen auf dem Bildschirm – dadurch wird das
cloudflaredPaket bereit und die Datei mit den Tunnel-Anmeldedaten generiert - Öffentliche Hostnamen hinzufügen:
| Subdomain | Domäne | Dienst |
|---|---|---|
<main-subdomain> |
your-domain.org |
http://localhost:8283 |
<share-subdomain> |
your-domain.org |
http://localhost:8283 |
Cloudflare erstellt die DNS-CNAME-Einträge automatisch – es sind keine manuellen DNS-Einträge erforderlich.
2c. SSL/TLS-Einstellung
Geh im Cloudflare-Dashboard für deine Domain zu SSL/TLS → Übersicht und stelle den Verschlüsselungsmodus auf „Vollständig (streng)“ ein.
Das bedeutet, dass Cloudflare das Let’s Encrypt-Zertifikat deines Servers bei der Verbindung überprüft. Dazu ist ein gültiges Zertifikat auf dem Server erforderlich (in Schritt 3 eingerichtet).
2d. Installiere „cloudflared“ auf dem Server
# Debian / Ubuntu
curl -L --output cloudflared.deb \
https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.deb
sudo dpkg -i cloudflared.deb
2e. Tunnel-Konfigurationsdatei
Erstelle ~/.cloudflared/config.yml:
tunnel: <tunnel-name>
credentials-file: /home/<user>/.cloudflared/<tunnel-id>.json
ingress:
- hostname: <main-subdomain>.your-domain.org
service: http://localhost:8283
- hostname: <share-subdomain>.your-domain.org
service: http://localhost:8283
- service: http_status:404 # catch-all: required, rejects unknown hostnames
no-autoupdate: true
Beide Hostnamen verweisen auf Nginx am Port 8283. Nginx leitet sie je nach server_name.
2f. Führe cloudflared als systemd-Dienst aus
sudo cloudflared service install
sudo systemctl enable --now cloudflared
# Verify
sudo systemctl status cloudflared
Schritt 3 – TLS-Zertifikat (Let’s Encrypt)
Das Zertifikat erfüllt zwei Zwecke:
- Aktiviert HTTPS auf Port 443 für den direkten Zugriff aus dem lokalen Netzwerk
- Erfüllt die „Full (strict)“-Validierung von Cloudflare, wenn eine Verbindung zu deinem Server hergestellt wird
sudo apt install certbot python3-certbot-nginx
sudo certbot certonly --nginx -d <main-subdomain>.your-domain.org
Mit dem --nginx Flag ermöglicht es Certbot, vorübergehend Nginx zu nutzen, um die ACME-Challenge abzuschließen. Das Zertifikat wird unter /etc/letsencrypt/live/<domain>/.
Automatische Verlängerung
Certbot installiert einen systemd-Timer, der das Zertifikat vor Ablauf automatisch erneuert. Nach der Erneuerung muss nginx neu geladen werden, damit das neue Zertifikat übernommen wird:
# Create a deploy hook so nginx reloads after every renewal
echo "systemctl reload nginx" | sudo tee /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh
sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh
# Verify the timer is active
sudo systemctl status certbot.timer
# Test renewal (dry run)
sudo certbot renew --dry-run
Schritt 4 – Nginx
Nginx verwaltet zwei separate Serverblöcke für Immich. Füge sie /etc/nginx/nginx.conf innerhalb des http {} Block.
map $http_upgrade $connection_upgrade { default upgrade; '' close; }
# --- Main Immich server ---
# Listens on 127.0.0.1:8283 (cloudflared) AND :443 (local network HTTPS)
server {
listen 127.0.0.1:8283;
listen 443 ssl http2;
server_name <main-subdomain>.your-domain.org;
ssl_certificate /etc/letsencrypt/live/<main-subdomain>.your-domain.org/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/<main-subdomain>.your-domain.org/privkey.pem;
client_max_body_size 50000M;
client_body_timeout 600s;
# Restore real visitor IP (cloudflared connects from localhost)
real_ip_header CF-Connecting-IP;
set_real_ip_from 127.0.0.1;
# Required for OAuth to generate correct redirect URLs
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Forwarded-Port 443;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# WebSocket support (required by Immich UI)
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_request_buffering off;
proxy_read_timeout 600s;
proxy_send_timeout 600s;
send_timeout 600s;
location / {
proxy_pass http://127.0.0.1:2283;
}
}
# --- Share-only domain ---
# Serves Immich share links only; all other paths return 403
server {
listen 8283;
server_name <share-subdomain>.your-domain.org;
set $backend "http://127.0.0.1:2283";
proxy_set_header Host $http_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;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_redirect off;
proxy_read_timeout 600s;
proxy_send_timeout 600s;
send_timeout 600s;
# Allow share paths and all static assets Immich needs to render them
location ~* ^/(share|api|_app|assets/|static/|fonts/|favicon.*\.png|apple-icon-180\.png|favicon\.ico|.*\.js|.*\.css|.*\.woff2?|.*\.ttf) {
proxy_pass $backend;
}
# Block everything else
location / {
return 403;
}
}
Testen und übernehmen:
sudo nginx -t && sudo systemctl reload nginx
Wichtige Hinweise zum Design
- Port 8283 ist die interne Übergabe zwischen Cloudflared und Nginx – er wird niemals nach außen freigegeben.
- Port 2283 ist Immich selbst – nur von localhost über Nginx erreichbar.
real_ip_header CF-Connecting-IPist unerlässlich. Ohne ihn werden Immich-Protokolle und jegliche Ratenbegrenzung127.0.0.1für jede Anfrage.X-Forwarded-Proto: httpsundX-Forwarded-Port: 443sind erforderlich, damit Immich OAuth korrekte Callback-URLs erstellen kann.- Die Whitelist für die „Share-only“-Domain muss
/apisowie die Dateiendungen für statische Dateien enthalten – Immich lädt Assets aus diesen Pfaden auch für Freigabeseiten. - Der
map $http_upgrade $connection_upgradeBlock muss auf derhttp {}Ebene deklariert werden, nicht innerhalb eines Server-Blocks. - Die Catch-All-Regel
- service: http_status:404am Ende der Cloudflared-Ingress-Regeln ist erforderlich – ohne ihn weigert sich Cloudflared, zu starten.









