Django Load Balancer Configuration Checklist
A checklist for putting Django behind a load balancer: health checks that pass host validation, forwarded headers and SECURE_PROXY_SSL_HEADER, correct client IPs, idle and request timeouts, connection draining, sticky sessions avoided, and TLS policy.
Problem statement
You are putting Django behind a load balancer (AWS ALB, Google Cloud Load Balancing, Azure Application Gateway, HAProxy, or a cloud provider's managed LB) to run several servers or containers. Typical first-day problems: targets marked unhealthy because health checks get 400 or 301, redirect loops because Django thinks requests are HTTP, every user sharing the load balancer's IP, uploads timing out, and dropped requests during deploys. Each has a specific setting.
- Health check a dedicated
/healthz/path that returns 200 without host or HTTPS redirect problems. - Set
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")and trust forwarded headers only from the load balancer. - Derive the client IP from
X-Forwarded-Forwith a known number of trusted proxies. - Order timeouts: Gunicorn timeout below the load balancer's idle timeout; enable connection draining longer than the slowest request.
- Avoid sticky sessions; store sessions in the database or Redis so any instance can serve any user.
Checklist
Health checks
- Path
/healthz/(liveness) or/healthz/ready(checks database), returning 200 quickly. - Exempt from
SECURE_SSL_REDIRECT(SECURE_REDIRECT_EXEMPT), or the check gets 301. - Host validation passes: health checks often send the target IP as
Host; answer them before host validation or allow the internal pattern. - Interval and thresholds tuned so a slow deploy is not marked unhealthy, but dead instances are removed within seconds.
Headers and client identity
- TLS terminated at the load balancer, forwarding
X-Forwarded-Proto; Django usesSECURE_PROXY_SSL_HEADER. - Backend instances accept traffic only from the load balancer (security groups), so forwarded headers cannot be spoofed.
- Client IP taken from the correct position in
X-Forwarded-For(Gunicorn--forwarded-allow-ips, DRFNUM_PROXIES, or a middleware with a fixed proxy count). USE_X_FORWARDED_HOSTonly if the load balancer rewrites Host; otherwise leave it off.
Timeouts and limits
- Gunicorn
timeoutlower than the load balancer idle timeout, so the app fails first with a clear error. - Keep-alive: Gunicorn
keepalivehigher than the load balancer's idle timeout when the LB reuses connections to targets (ALB requires this to avoid sporadic 502s). - Request body size limits consistent between load balancer, Nginx, and Django.
- WebSocket paths configured for long idle times if used.
Deploys and sessions
- Connection draining or deregistration delay longer than the slowest normal request.
- Rolling deploys keep enough healthy targets (minimum healthy percent).
- No sticky sessions required: sessions in database, cache, or signed cookies; uploads in shared storage.
- Static files served by WhiteNoise, a CDN, or each instance identically, never from one instance's disk.
TLS
- Modern TLS policy on the listener (TLS 1.2+), HTTP redirected to HTTPS at the load balancer or by Django.
- Certificates managed by the provider with auto-renewal, covering every hostname.
- HSTS set in Django once HTTPS is stable on all names.
Explanation
A load balancer adds a hop between clients and Django: it terminates TLS, rewrites the client address into headers, decides health on its own schedule, and holds connections with its own timeouts. Django and Gunicorn must be configured to interpret that hop: trust the forwarded scheme and client address from the load balancer only, answer health checks cleanly, and time out and drain in an order that never leaves the load balancer talking to a closed connection.
Edge cases / notes
- Network load balancers. Layer 4 balancers pass TCP through; use the PROXY protocol for client IPs and terminate TLS in Nginx.
- Multiple proxies. CDN plus load balancer plus Nginx means three hops in
X-Forwarded-For; count them. - Internal services. Internal load balancers for service-to-service traffic need the same health and timeout care.
Next steps
- Plan scaling with Scale Django Horizontally With a Load Balancer.
- Fix failing checks with Fix Django Health Check Failing Behind a Load Balancer.
- Get client IPs right with Fix Django Wrong Client IP Behind a Proxy.
FAQ
Why does Django redirect forever behind the load balancer?
It does not know the original request was HTTPS. Set SECURE_PROXY_SSL_HEADER for the forwarded proto header.
Why are my targets unhealthy?
The health check probably receives a 301 or a 400 DisallowedHost. Use a dedicated path exempt from both.
Do I need sticky sessions for Django?
No, if sessions live in the database, cache, or signed cookies and files are in shared storage.
What causes random 502s behind an AWS ALB?
Often Gunicorn's keepalive being shorter than the ALB's idle timeout. Make keepalive longer.