Guides / Django generating http:// links behind a proxy (redirect_uri_mismatch)

Django generating http:// links behind a proxy (redirect_uri_mismatch)

The site is on https, but links in password reset emails start with http://, request.build_absolute_uri() returns an http address, or Google and GitHub login fails with redirect_uri_mismatch. Django builds URLs from what it sees in the request, and behind a proxy it sees http.

What it means

When a load balancer, nginx or a CDN ends HTTPS, it talks to Django over plain http. Django has no way to know the browser used https unless the proxy tells it, and unless you tell Django to believe the proxy. So request.is_secure() is false and every absolute URL Django generates starts with http://.

Why it happens

  1. SECURE_PROXY_SSL_HEADER isn't set. The proxy sends X-Forwarded-Proto: https, but Django ignores it by default.
  2. The proxy doesn't send the header, or sends it on some paths only.
  3. The host is wrong, not only the scheme: the proxy rewrites Host to an internal name, so URLs point at an address nobody can reach.
  4. The Sites framework supplies the domain. Emails built from Site.objects.get_current() use whatever domain (example.com by default) is stored in the database.
  5. A package builds URLs on its own. Social login libraries (allauth and similar) have their own setting for the scheme, and send redirect_uri to the provider.

The fix

# settings.py
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
USE_X_FORWARDED_HOST = True   # only if your proxy sets X-Forwarded-Host and overwrites any client value
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;

Only trust these headers when the proxy always overwrites them. A client-supplied value would otherwise be believed (Django's warning).

Then verify what Django sees, in a shell on the server or a temporary view:

print(request.is_secure(), request.get_host(), request.build_absolute_uri("/"))
# expect: True your-domain.com https://your-domain.com/

For the Sites framework, fix the stored domain:

python manage.py shell -c "from django.contrib.sites.models import Site; Site.objects.update_or_create(id=1, defaults={'domain': 'example.com', 'name': 'Example'})"

For social login libraries, set their scheme option too (for django-allauth, the HTTP protocol setting; see its documentation for the current name) and register the exact https redirect URI with the provider.

Making it stick in production

Or skip it

Django Cloud ends HTTPS at the load balancer and sets SECURE_PROXY_SSL_HEADER for you when your settings don't, so requests that came in over https are treated as 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.