Skip to content

Reverse proxy

A reverse proxy sits in front of Frameleaf and passes requests on to it. It’s the usual way to serve Frameleaf over HTTPS on your own domain, such as https://photos.example.com, and it can handle certificates, load balancing and access control for you.

If you’d rather not run one, Frameleaf Cloud remote access gives you HTTPS and a stable address without opening ports. See Remote access for all the options.

  • Forward all headers, and set Host, X-Real-IP, X-Forwarded-Proto and X-Forwarded-For to the right values.
  • Allow large uploads. Videos can be many gigabytes.
  • Long timeouts. Large uploads fail if the proxy gives up after a minute.
  • WebSockets. The web app uses them for live updates.
  • Serve Frameleaf at the root of a domain or subdomain. Frameleaf can’t be served on a sub-path such as https://example.com/photos.

Then tell Frameleaf to trust the proxy, so it sees each visitor’s real address. Add the proxy’s IP to FRAMELEAF_TRUSTED_PROXIES in .env, comma-separated if there are several, and run docker compose up -d. See Environment variables.

Finally, set Public server URL to your new address in Settings, then Frameleaf Cloud, then Remote access, so shared links and emails use it. It doesn’t need a Frameleaf Cloud link. If you use your own sign-in provider, add the new address to its redirect URIs.

Replace <public_url> with your domain and <backend_url> with the address of the Frameleaf server.

server {
server_name <public_url>;
# allow large file uploads
client_max_body_size 50000M;
# don't buffer uploads, to save memory on the proxy and keep uploads fast
proxy_request_buffering off;
# a larger body buffer avoids limiting upload speed
client_body_buffer_size 1024k;
# headers
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;
# websockets
proxy_http_version 1.1;
proxy_redirect off;
# timeouts
proxy_read_timeout 600s;
proxy_send_timeout 600s;
send_timeout 600s;
location / {
proxy_pass http://<backend_url>:2283;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
# useful with the Let's Encrypt http-01 challenge
# location = /.well-known/immich {
# proxy_pass http://<backend_url>:2283;
# }
}

Caddy sets up HTTPS for you automatically:

photos.example.com {
reverse_proxy http://<backend_url>:2283
}
<VirtualHost *:80>
ServerName <public_url>
ProxyRequests Off
# timeout in seconds
ProxyPass / http://127.0.0.1:2283/ timeout=600 upgrade=websocket
ProxyPassReverse / http://127.0.0.1:2283/
ProxyPreserveHost On
</VirtualHost>

This example is for Traefik version 3. The most important part is raising the entry point’s timeouts. The default of 60 seconds stops video uploads after a minute with error 499; this raises it to 10 minutes, which is usually enough.

In traefik.yaml:

entryPoints:
websecure:
address: :443
# add this section
transport:
respondingTimeouts:
readTimeout: 600s
idleTimeout: 600s

Then add labels to the server in Frameleaf’s docker-compose.yml:

services:
immich-server:
labels:
traefik.enable: true
traefik.http.routers.frameleaf.entrypoints: websecure
traefik.http.routers.frameleaf.rule: Host(`photos.example.com`)
traefik.http.services.frameleaf.loadbalancer.server.port: 2283

Traefik needs to reach the network Frameleaf runs on, usually by adding Traefik’s network to the immich-server service.