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.

Django behind a load balancerHealth checksHost, no redirectForwarded headersproto, client IPTimeoutsidle vs GunicornDrainingdeploys without dropsMost load balancer problems are header, health check, or timeout mismatches.
  1. Health check a dedicated /healthz/ path that returns 200 without host or HTTPS redirect problems.
  2. Set SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https") and trust forwarded headers only from the load balancer.
  3. Derive the client IP from X-Forwarded-For with a known number of trusted proxies.
  4. Order timeouts: Gunicorn timeout below the load balancer's idle timeout; enable connection draining longer than the slowest request.
  5. 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 uses SECURE_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, DRF NUM_PROXIES, or a middleware with a fixed proxy count).
  • USE_X_FORWARDED_HOST only if the load balancer rewrites Host; otherwise leave it off.
LB setting and Django sideLoad balancerDjango / GunicornTLS terminationHTTPS listenerSECURE_PROXY_SSL_HEADERX-Forwarded-Forappended per hoptrusted proxy countHealth check path/healthz/no redirect, no host errorIdle timeout60 s (ALB default)timeout below itDeregistration delay30 sgraceful_timeoutEach load balancer feature needs a matching application settingdjango-deployment.com
Mismatches show up as 502s, redirect loops, or wrong client IPs.

Timeouts and limits

  • Gunicorn timeout lower than the load balancer idle timeout, so the app fails first with a clear error.
  • Keep-alive: Gunicorn keepalive higher 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.
symptom and causeSymptomCause and fixtargets unhealthyhealth check gets 400 or 301; fix pathredirect loopproto header ignored;SECURE_PROXY_SSL_HEADERsporadic 502skeepalive shorter thanLB idle; raise itall users same IPforwarded IP not used;trust proxy countFour settings explain most first-day load balancer problemsdjango-deployment.com
Check load balancer access logs alongside Django logs to tell these apart.

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

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.

Keep going