Running YAFFA behind a custom reverse proxy
If you terminate HTTPS at your own reverse proxy — Nginx Proxy Manager, Traefik, plain nginx, Apache, a cloud load balancer, etc. — follow this guide to get URL generation and API requests working correctly and avoid mixed-content errors.
The Docker Compose setup includes an optional Caddy service that can handle HTTPS for you. It is part of the https Compose profile, so it only starts with docker compose --profile https up -d, and you also need to set APP_PORT in your .env so that Caddy is the only service publishing ports 80 and 443. If you enable it and follow its setup steps, you generally don't need this guide. This page is for administrators terminating HTTPS at their own, separately managed proxy.
Why this happens
In this deployment shape:
- HTTPS is terminated at your reverse proxy.
- YAFFA itself receives the request over plain HTTP from the proxy.
- The proxy must tell YAFFA the original request was HTTPS by sending an
X-Forwarded-Proto: httpsheader. - YAFFA must be configured to trust that proxy before it will believe the header.
Both pieces are required, and neither compensates for the other:
- Setting
TRUSTED_PROXIESdoesn't help if the proxy never sendsX-Forwarded-Proto. - A proxy sending
X-Forwarded-Protodoesn't help if YAFFA isn't configured to trust it.
If either is missing, YAFFA generates http:// links and API URLs even though your site is served over HTTPS, and browsers block those requests as mixed content. No application code changes are needed to fix this — it's purely a matter of configuring TRUSTED_PROXIES and your proxy correctly.
TRUSTED_PROXIES
TRUSTED_PROXIES in .env accepts:
*or**— trust the directly connecting peer (i.e. whatever IP the request physically arrives from).- A single IPv4 or IPv6 address.
- A CIDR range (e.g.
10.0.0.0/8). - Multiple addresses/ranges, separated by commas — whitespace around entries is trimmed automatically, so
1.2.3.4, 5.6.7.8works the same as1.2.3.4,5.6.7.8.
An entry that isn't a valid IP or CIDR range is simply never matched — it won't crash YAFFA and won't grant trust to anything, so a typo here fails closed rather than open.
Prefer listing your proxy's specific IP/CIDR where practical. */** is appropriate when YAFFA itself isn't directly reachable by untrusted clients (for example, only the proxy can reach the container's port) — trusting forwarded headers is a security boundary, since anyone who can send YAFFA a request directly could otherwise forge X-Forwarded-Proto/X-Forwarded-For and spoof their scheme or IP.
.env settings
APP_URL=https://yaffa.example.com
TRUSTED_PROXIES=* # or a specific IP/CIDR, e.g. 172.18.0.0/16
APP_URLmust be the externally visiblehttps://URL. It addresses a different concern thanTRUSTED_PROXIES:APP_URLis the base URL YAFFA uses when it can't otherwise determine the scheme, whileTRUSTED_PROXIESis what lets YAFFA trust the actual forwarded scheme on a per-request basis. Setting one doesn't replace the other.
SANCTUM_STATEFUL_DOMAINS and SESSION_DOMAIN are not required for every reverse-proxy setup — only set them if YAFFA is exposed on a different subdomain or port than a default same-origin deployment would use:
# Only needed if you're accessing YAFFA on a non-default subdomain or port
SANCTUM_STATEFUL_DOMAINS=yaffa.example.com
SESSION_DOMAIN=.example.com
Restart the containers after changing .env — a mere restart doesn't reload environment variables. Run:
docker compose down && docker compose up -d
Reverse proxy configuration
Plain nginx (or anything nginx-based)
location / {
proxy_pass http://<yaffa-app-host>:80;
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;
proxy_set_header X-Forwarded-Host $host;
}
Nginx Proxy Manager
The "Details" tab of a Proxy Host (scheme, forward host/port) doesn't always guarantee X-Forwarded-Proto is sent. If YAFFA still receives requests as plain HTTP, add a Custom Nginx Configuration block on the Proxy Host with:
proxy_set_header X-Forwarded-Proto $scheme;
Traefik
- "traefik.http.routers.yaffa.tls=true"
- "traefik.http.services.yaffa.loadbalancer.server.port=80"
Traefik is expected to set X-Forwarded-Proto automatically when it terminates TLS. Behavior can vary with custom middleware or unusual routing setups, so if you hit mixed-content issues, verify the header is actually arriving (see Troubleshooting below).
Your own Caddy instance
If you're running Caddy yourself (outside of the Docker Compose setup's optional Caddy service), it's expected to set X-Forwarded-Proto automatically as well. As with Traefik, verify this if you run into issues — custom Caddyfile directives can change this behavior.
Troubleshooting: Mixed Content / broken API calls
If the app loads and login works, but AJAX calls fail with a browser console error like:
Mixed Content: The page at 'https://yaffa.example.com/' was loaded over HTTPS,
but requested an insecure XMLHttpRequest endpoint 'http://yaffa.example.com/api/...'.
This request has been blocked.
check the following, in order:
- Confirm
APP_URLuseshttps://in your.envfile. - Confirm
TRUSTED_PROXIESis set and contains your proxy's address/range (or intentionally uses*/**). - Confirm the proxy actually sends
X-Forwarded-Proto: httpsto YAFFA. This is the hop between your proxy and the YAFFA container, not the one between the browser and your proxy, so the browser's network tab won't show it. Check your reverse proxy's own configuration and access/request logs, or run a request against the YAFFA container directly (e.g.curl -vfrom the proxy host) to confirm the header is present. Avoid turning onAPP_DEBUGon a production instance just to inspect this. - Recreate the containers after any
.envchange —docker compose down && docker compose up -d, not justrestart.
If you've confirmed all of the above and still see the issue, please open an issue with your proxy type and configuration.