Skip to Content
DeploymentReverse Proxy

Reverse Proxy

For production deployments, place PicPeak behind a reverse proxy for SSL termination and routing.

The routing below applies to the multi-container Compose stack, where the frontend and backend are separate services. The all-in-one image serves everything from one port, so a proxy in front of it needs a single rule pointing at port 3000, and the container needs TRUST_PROXY=1 (see below) — see Example: NAS at Home for a worked setup with Nginx Proxy Manager.

Routing schema

PathServicePortDescription
/api/*Backend3001All API endpoints
/photos/*Backend3001Protected photo files
/thumbnails/*Backend3001Protected thumbnail files
/uploads/*Backend3001Upload files
/* (everything else)Frontend3000React SPA (including /admin/*, /gallery/*)

The /admin/* routes are served by the frontend (React SPA), not the backend. The backend only handles /api/admin/* requests.

The production compose file publishes the backend port 3001 on host loopback (127.0.0.1) only. A proxy on the same host reaches it at localhost:3001 as below; a proxy on another machine cannot, unless you set PICPEAK_BACKEND_BIND_ADDRESS. The simpler alternative is a single rule that sends everything to the frontend port 3000, whose nginx forwards /api and the media paths to the backend itself — then set TRUST_PROXY=2 (see below).

Create /etc/nginx/sites-available/picpeak:

server { listen 80; server_name your-domain.com; return 301 https://$server_name$request_uri; } server { listen 443 ssl http2; server_name your-domain.com; ssl_certificate /etc/letsencrypt/live/your-domain.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/your-domain.com/privkey.pem; # Backend: API endpoints location /api/ { proxy_pass http://localhost:3001; 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; } # Backend: Protected media files location ~ ^/(photos|thumbnails|uploads)/ { proxy_pass http://localhost:3001; 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; } # Frontend: Everything else (React SPA) location / { proxy_pass http://localhost:3000; 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; } }

Enable the site:

sudo ln -s /etc/nginx/sites-available/picpeak /etc/nginx/sites-enabled/ sudo nginx -t sudo systemctl reload nginx

For video uploads, increase the body size limit:

client_max_body_size 10G; proxy_read_timeout 3600; proxy_send_timeout 3600;

Client IP, rate limiting and TRUST_PROXY

PicPeak keys its API rate limiter and its login lockout on the client IP. Behind a proxy the backend only sees the real client address when two things hold: the proxy forwards X-Forwarded-For (all configurations above do), and PicPeak trusts exactly the proxies in front of it.

TRUST_PROXY is the number of proxy hops PicPeak trusts. The production compose file defaults it to 1, which is the bundled frontend nginx. Count the proxies between the client and the backend on the path your /api traffic takes:

LayoutHopsTRUST_PROXY
No proxy of your own; clients open the frontend portfrontend nginx1 (default)
Your proxy forwards everything to the frontend port 3000 (the installer’s reverse-proxy layout, Cloudflare Tunnel, Nginx Proxy Manager)your proxy, frontend nginx2
Your proxy routes /api and the media paths straight to the backend, as in the configurations aboveyour proxy1 (default)
All-in-one image behind your proxyyour proxy1 (the image defaults to 0)
# .env — your proxy in front of the frontend port TRUST_PROXY=2

Set it before you update an install that sits behind a proxy and has no TRUST_PROXY in its .env (beta from v3.174.2-beta.0, stable from v3.134.7). With too low a value every visitor presents as the proxy’s address, shares one rate-limit budget of 300 requests per 15 minutes, and five failed logins from anyone lock out logins for everyone for 15 minutes. When TRUST_PROXY is 1 and a request shows another private-range proxy in front of nginx, the backend logs one warning suggesting 2.

A hop count is only safe when clients cannot take a shorter path to the app, which is why the backend port is published on host loopback only (PICPEAK_BACKEND_BIND_ADDRESS). With a proxy on the same host, also set PICPEAK_BIND_ADDRESS=127.0.0.1 so the plain-HTTP frontend port is not reachable around it. Do not set TRUST_PROXY=true unless every hop in front of PicPeak is yours: a trusted-all setting lets any client spoof its address through X-Forwarded-For.

See Environment Variables → TRUST_PROXY.

Running behind Cloudflare? Bots and Scanners has the ready-made TRUST_PROXY line.

Mixed HTTPS + LAN HTTP access

Some self-hosted setups want two working entry points to the same PicPeak instance:

  • Public HTTPS via the reverse proxy on a domain (https://photos.example.com)
  • Direct HTTP on the LAN for admin access (http://192.168.1.50:3000, the frontend port)

This works with the default COOKIE_SECURE=auto in production. The backend reads req.secure from Express: the Secure cookie flag is set when the request arrived over HTTPS (via a trusted proxy forwarding X-Forwarded-Proto: https) and left off otherwise. Both public HTTPS login and LAN HTTP login work with correctly-flagged cookies.

# .env — the default; only needed if you set something else before COOKIE_SECURE=auto

Two conditions:

  • The proxy must be inside the TRUST_PROXY boundary described above. If it is not, every request counts as plain HTTP and the cookies lose their Secure flag.
  • The LAN entry point is the frontend port. The backend port 3001 is published on host loopback only; set PICPEAK_BACKEND_BIND_ADDRESS to reach it from another machine.

Keep PICPEAK_BIND_ADDRESS at its default (0.0.0.0) in this setup, since the LAN needs the frontend port. If the site is only ever reached over HTTPS, set COOKIE_SECURE=true instead.

The Nginx, Traefik, and Caddy configurations shown above already forward X-Forwarded-Proto correctly.

See Environment Variables → Authentication cookies for the full reference.

Last updated on