Guides / CSRF verification failed. Request aborted.
CSRF verification failed. Request aborted.
Forms and logins worked locally. After deploying behind HTTPS, every POST ends in a 403 page that says CSRF verification failed. Request aborted. Django 4.0 and newer check the browser's Origin header against the address it thinks it is served on, and behind a proxy it often has that address wrong. The line under "Reason given for failure" tells you which cause you have.
What it means
Django's CSRF protection rejects a POST unless it carries a token that matches a cookie, and, on HTTPS, comes from an origin Django trusts. The generic message is the same for every failure. The reason is the useful part. With DEBUG = True it is on the page. In production it is in your logs, under the django.security.csrf logger:
Forbidden (Origin checking failed - https://example.com does not match any trusted origins.): /accounts/login/
Why it happens
Match the reason to the cause. They are listed from most to least common after a first deploy.
- "Origin checking failed - https://example.com does not match any trusted origins." You are behind a proxy or load balancer that ends HTTPS. The browser sends
Origin: https://example.com, but the proxy talks to Django over plain http, so Django believes the site ishttp://example.comand the two don't match. - "CSRF cookie not set." The browser did not send the CSRF cookie back. Typical reasons:
CSRF_COOKIE_SECURE = Truewhile the user is onhttp://(mixed http and https), aCSRF_COOKIE_DOMAINthat does not match the domain, or a CDN or page cache that served a cached copy of the form. - "Referer checking failed - no Referer." On HTTPS Django also checks the
Refererheader. AReferrer-Policy: no-referrerheader, set by your proxy or bySECURE_REFERRER_POLICY, strips it. - "CSRF token missing." The form has no
{% csrf_token %}, or your JavaScript posts without theX-CSRFTokenheader. - Posts from another origin, such as a separate front-end domain, a subdomain, or an admin on a different host. Those origins have to be trusted explicitly.
The fix
1. Behind a proxy: tell Django the request was HTTPS
This is the real fix for the first cause, and it also fixes request.is_secure(), the scheme in build_absolute_uri() and secure cookies. Set SECURE_PROXY_SSL_HEADER to the header your proxy sets:
# settings.py
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
Only do this if the proxy always sets or overwrites that header. If clients can send their own X-Forwarded-Proto, they can pretend to be secure. With nginx in front, add this to the location block that proxies to Django:
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
2. Trust your origins explicitly
Add your site's origins to CSRF_TRUSTED_ORIGINS. Since Django 4.0 every entry must include the scheme. A bare example.com stops Django from starting with (4_0.E001) As of Django 4.0, the values in the CSRF_TRUSTED_ORIGINS setting must start with a scheme.
CSRF_TRUSTED_ORIGINS = [
"https://example.com",
"https://www.example.com",
"https://*.example.com", # every subdomain, if you need it
]
This is also what you need for a front end on another origin, and when a proxy rewrites the Host header so that fix 1 alone isn't enough. A trusted origin only affects the Origin and Referer checks; the token and cookie are still required.
3. Mixed http and https: send everyone to https
If the cookies are secure-only, people on http:// will never get a working form. Redirect to https, and mark the cookies secure once that works:
SECURE_SSL_REDIRECT = True # needs fix 1, or it loops forever
CSRF_COOKIE_SECURE = True
SESSION_COOKIE_SECURE = True
Set SECURE_PROXY_SSL_HEADER first. Without it Django sees every request as http, redirects it to https, and the browser ends up in a redirect loop.
4. Cookie, token and referrer problems
- Put
{% csrf_token %}inside every POST form. For AJAX, send the cookie's value in theX-CSRFTokenheader, as the Django docs show. - Don't cache pages that contain a form for anonymous users on your CDN, or vary the cache on the CSRF cookie.
- Leave
CSRF_COOKIE_DOMAINunset unless you share the cookie between subdomains. - If you set
SECURE_REFERRER_POLICYyourself, don't use"no-referrer". Django's default,"same-origin", works. - Use
@csrf_exemptonly for views that don't use cookie authentication, such as a webhook that verifies its own signature.
Making it stick in production
Read the origins from the environment so staging and production don't share a list:
import os
CSRF_TRUSTED_ORIGINS = [
origin.strip()
for origin in os.environ.get("DJANGO_CSRF_TRUSTED_ORIGINS", "").split(",")
if origin.strip()
]
export DJANGO_CSRF_TRUSTED_ORIGINS="https://example.com,https://www.example.com"
Then run python manage.py check --deploy. It flags a missing SECURE_SSL_REDIRECT, insecure session and CSRF cookies, and missing HSTS (deployment checklist). To see what Django thinks of a request, temporarily log request.is_secure() and request.get_host() in a view: behind a correct setup they are True and your public domain.
If the 403 is replaced by a 400 page about the host, see DisallowedHost: Invalid HTTP_HOST header.
Questions
Do I need CSRF_TRUSTED_ORIGINS if SECURE_PROXY_SSL_HEADER is set?
Usually not. With the header set, Django sees the request as https and the Origin matches the Host. You still need CSRF_TRUSTED_ORIGINS when the form is posted from a different origin, such as a separate front end, or when the proxy rewrites the Host header.
Can I just disable CSRF protection?
No. The check protects logged-in users from forms submitted by other sites. Use @csrf_exempt only on views that do not rely on cookies, such as a webhook that verifies its own signature.
Or skip it
Django Cloud ends HTTPS at the load balancer and sets SECURE_PROXY_SSL_HEADER for you when your settings don't, and it adds the https:// address of your app and of each custom domain to CSRF_TRUSTED_ORIGINS. Forms keep working on the first deploy without touching either setting.
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.