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
SECURE_PROXY_SSL_HEADERisn't set. The proxy sendsX-Forwarded-Proto: https, but Django ignores it by default.- The proxy doesn't send the header, or sends it on some paths only.
- The host is wrong, not only the scheme: the proxy rewrites
Hostto an internal name, so URLs point at an address nobody can reach. - The Sites framework supplies the domain. Emails built from
Site.objects.get_current()use whatever domain (example.comby default) is stored in the database. - A package builds URLs on its own. Social login libraries (allauth and similar) have their own setting for the scheme, and send
redirect_urito 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
- Add a test that renders a password-reset email through the test client with
secure=Trueand asserts it containshttps://. - Set
CSRF_COOKIE_SECUREandSESSION_COOKIE_SECUREas well; they depend on the same detection. - Run
python manage.py check --deploy. - Forms failing with a 403 on https have the same root cause: CSRF verification failed. A redirect loop is the next stop: too many redirects.
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
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.