You need CSRF_TRUSTED_ORIGINS when a POST, PUT or DELETE comes from a different origin than your Django site, for example a form on another subdomain, or when a proxy changes the host Django sees. Add exact origins with the scheme, such as https://app.example.com, and keep the list short.
What Django checks
Django's CSRF documentation explains that CsrfViewMiddleware verifies the Origin header, when the browser provides it, against the current host and the CSRF_TRUSTED_ORIGINS setting. This provides protection against cross subdomain attacks. For HTTPS requests with no Origin header, it performs strict referer checking, which means that even if a subdomain can set or modify cookies on your domain, it cannot force a user to post to your application, since the request will not come from your exact domain.
The settings reference describes the default as an empty list, and says the setting is a list of trusted origins for unsafe requests such as POST. Safe methods are ignored, because requests that Django treats as safe should have no dangerous side effects.
The example from the docs
Django gives an example with two hosts. Without the setting, a POST from subdomain.example.com to api.example.com fails, which is the protection working. If you need cross origin unsafe requests, add the trusted origin to the list. The docs say to add the origin with its scheme, and to include the http version only if requests originate from an insecure page:
CSRF_TRUSTED_ORIGINS = ["https://subdomain.example.com"]
The setting also supports subdomain wildcards such as https://*.example.com, which allows access from all subdomains of example.com.
Why wildcards are a risk
A wildcard trusts every current and future subdomain. If any subdomain hosts user content, a forgotten app, or a page that can be taken over, an attacker can use it to send forged requests to the main site, which is exactly the cross subdomain attack the check exists to prevent. Prefer a list of exact origins.
| Entry | Meaning | Risk |
|---|---|---|
| https://app.example.com | One exact origin | Low |
| https://*.example.com | Every subdomain | Higher, depends on every subdomain's security |
| http://app.example.com | Insecure origin | Only if you truly have insecure pages, avoid |
The proxy case
A common reason people hit CSRF failures after deploying is a proxy. If Django does not see the public host or scheme correctly, the origin check compares the wrong values. The right fix is usually correct host and proxy header configuration, not a broad trusted list. See the forwarded headers post in this series for how SECURE_PROXY_SSL_HEADER and ALLOWED_HOSTS fit in. Django's CSRF page also says the protection assumes validation of the Host header and that you use HTTPS with HSTS, because CSRF protection cannot protect against man in the middle attacks.
Setting it safely
- Start with an empty list. Add only origins you can name.
- Include the scheme. An entry without one is not what you meant.
- Avoid wildcards unless you control every subdomain.
- Avoid http origins in production.
- Test with a real browser: submit a form from the allowed origin, then from a disallowed one, and confirm the second fails.
- Do not disable CSRF protection on a view to make an error go away.
Try it yourself
Click through it at your own pace, or open it full screen. See how a running app is tested for missing request forgery protections.
Quick check
Does CSRF_TRUSTED_ORIGINS apply to GET requests?
No. Django ignores safe methods in CSRF checks. The setting concerns unsafe requests such as POST.
Is a wildcard subdomain entry a good default?
No. It trusts every subdomain, so the weakest one sets your security. List exact origins.
Safeguard's DAST scanning and policy gates can help catch weakened request forgery settings. You can try a free scan.
Sources
Last reviewed: 19 October 2026.