Running behind a proxy
TLS, forwarded headers and trusted proxies.
The app sets its own security headers and rate limits, so it is safe with no proxy in front, and a proxy only adds a layer. To expose it, terminate TLS in your proxy (nginx, Traefik, HAProxy, a cloud load balancer) and point it at the app. The app needs the real client address for rate limits and sign-in protection; it reads a forwarded header only when the request comes from a trusted proxy:
| In front of the app | CLIENT_IP_HEADER | TRUSTED_PROXIES |
|---|---|---|
nothing (app exposed directly, HTTP_BIND=0.0.0.0) | (empty) | (unused) |
| one proxy | X-Forwarded-For | the proxy's address or network |
nginx with proxy_set_header X-Real-IP $remote_addr | X-Real-IP | the proxy's address |
| Cloudflare, then a proxy | CF-Connecting-IP | the proxy's network |
With X-Forwarded-For, the client is the rightmost address that is not a trusted proxy, so a client cannot pose as someone else by writing the header. The default trusted list is loopback and private networks, where a container proxy usually lives; if the app itself is reachable from a private network, narrow it to the proxy's exact address. Cookies are marked Secure when the trusted proxy says the request came over HTTPS. For a proxy that discovers services by container labels, add its labels and network to your docker-compose.yml. Proxy-level rate limits (for example nginx limit_req) are welcome as a second layer.