Guides / Too many redirects with SECURE_SSL_REDIRECT

Too many redirects with SECURE_SSL_REDIRECT

You turned on SECURE_SSL_REDIRECT = True and now the browser says "This page isn't working: redirected you too many times" (ERR_TOO_MANY_REDIRECTS). Django thinks every request is plain http, so it redirects to https, and the load balancer sends the next request in over http again. Tell Django to trust the proxy's scheme header and the loop ends.

What it means

SECURE_SSL_REDIRECT redirects any request that Django considers insecure to https. Behind a load balancer, reverse proxy or CDN that ends HTTPS, the connection between the proxy and Django is plain http. Django looks at that connection, sees http, and redirects forever.

Why it happens

  1. SECURE_PROXY_SSL_HEADER is not set, so request.is_secure() is always false behind the proxy.
  2. The proxy does not send the header. You set the setting, but nginx or the load balancer never adds X-Forwarded-Proto.
  3. A CDN talks http to your origin. Cloudflare's "Flexible" SSL mode connects to your server over http even though visitors use https. Use "Full" or "Full (strict)", or rely on the header.
  4. The health check gets redirected. The load balancer requests / over http, gets a 301, and marks the app unhealthy (a different symptom of the same cause).
  5. Both the proxy and Django redirect, and they disagree about the host.

The fix

# settings.py
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
SECURE_SSL_REDIRECT = True

Make sure the proxy sets the header, and overwrites any value sent by the client (why this matters). With nginx:

proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $host;

Most cloud load balancers add X-Forwarded-Proto for you. To verify what reaches Django, run curl -sI http://example.com/: you should get one 301 to the https URL, and curl -sI https://example.com/ should return 200 and no Location header.

If the health check calls your app over plain http, exempt its path from the redirect. The entries are regular expressions matched against the path without its leading slash:

SECURE_REDIRECT_EXEMPT = [r"^healthz/$"]

Making it stick in production

Forms still failing with a 403 after the loop is gone? See CSRF verification failed. Request aborted.

Or skip it

Django Cloud serves your app over HTTPS and ends it at the load balancer, and sets SECURE_PROXY_SSL_HEADER for you when your settings don't, so Django knows which requests were secure.

pip install djangocloud-cli
djangocloud deploy

Get startedSee pricing

Free plans are available for students, AWS Activate startups, open-source, non-profit and research projects. See how Django Cloud compares with other ways to host Django.

Django is a registered trademark of the Django Software Foundation. Django Cloud is not affiliated with or endorsed by it. Found a mistake in this guide? Tell us and we will fix it.