Security in Django is not a feature you bolt on at the end. The framework provides strong defaults, but those defaults only protect you if you configure them correctly and avoid the patterns that bypass them. This guide is the security checklist I run through before every production deployment: HTTPS enforcement, CSRF and XSS protections, SQL injection prevention, authentication hardening, security headers, secret management, dependency auditing, and the Django check --deploy command that catches common misconfigurations. For a broader security perspective, see the Security hub.
Django’s security track record is strong. The framework handles most common web vulnerabilities automatically when used correctly. The mistakes that lead to breaches are almost always configuration oversights or developers deliberately bypassing protections.
HTTPS everywhere
Every production Django site must run on HTTPS. Configure Django to enforce it:
SECURE_SSL_REDIRECT = True
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
Enable HSTS to tell browsers to always use HTTPS:
SECURE_HSTS_SECONDS = 31536000 # 1 year
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True
If you are behind a load balancer or reverse proxy, SECURE_PROXY_SSL_HEADER ensures Django recognizes HTTPS requests correctly. Without it, redirect loops occur because Django sees all traffic as HTTP.
CSRF protection
Django’s CSRF middleware is enabled by default. Keep it that way. Every POST form must include {% csrf_token %}:
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit">Save</button>
</form>
For AJAX requests, include the CSRF token in headers:
const csrfToken = document.querySelector('[name=csrfmiddlewaretoken]').value;
fetch('/api/data/', {
method: 'POST',
headers: {
'X-CSRFToken': csrfToken,
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
});
Set CSRF_TRUSTED_ORIGINS for Django 4.0+:
CSRF_TRUSTED_ORIGINS = ['https://prodjango.com']
Never disable CSRF protection, even for API endpoints. Use @csrf_exempt only for genuinely stateless webhook receivers with their own authentication.
XSS prevention
Django auto-escapes template variables by default. This prevents most XSS attacks:
<!-- Safe: automatically escaped -->
<p>{{ user_input }}</p>
<!-- Dangerous: bypasses escaping -->
<p>{{ user_input|safe }}</p>
Rules for XSS prevention:
- Never use
|safeormark_safe()on user-provided content - Sanitize rich text input with a library like
bleachbefore storage - Set
Content-Security-Policyheaders to restrict script sources - Use
httponlycookies so JavaScript cannot access session tokens
SQL injection protection
Django’s ORM parameterizes all queries automatically, preventing SQL injection. But you can still introduce vulnerabilities with:
# DANGEROUS: string interpolation in raw SQL
cursor.execute(f"SELECT * FROM users WHERE email = '{email}'")
# SAFE: parameterized query
cursor.execute("SELECT * FROM users WHERE email = %s", [email])
Rules:
- Never use f-strings or
.format()in raw SQL - Always use parameterized queries with
%splaceholders - Use the ORM for standard CRUD operations
- Review any
extra(),raw(), orcursor.execute()calls carefully
Security headers
Configure all security-relevant headers:
# settings/production.py
SECURE_CONTENT_TYPE_NOSNIFF = True
SECURE_BROWSER_XSS_FILTER = True
X_FRAME_OPTIONS = 'DENY'
Add a Content Security Policy through middleware or your web server:
Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self';
Set Referrer-Policy and Permissions-Policy in your web server configuration or through Django middleware. The production settings guide covers header configuration in more detail.
Cookie security
Secure all cookies:
SESSION_COOKIE_SECURE = True
SESSION_COOKIE_HTTPONLY = True
SESSION_COOKIE_SAMESITE = 'Lax'
CSRF_COOKIE_SECURE = True
CSRF_COOKIE_HTTPONLY = True
SECURE ensures cookies are only sent over HTTPS. HTTPONLY prevents JavaScript access. SAMESITE protects against cross-site request attacks.
Secret management
- Generate
SECRET_KEYwith at least 50 random characters - Never commit secrets to version control
- Use environment variables or a secrets manager
- Rotate keys if they are ever exposed
- Use different secrets for different environments
import os
SECRET_KEY = os.environ['DJANGO_SECRET_KEY']
Dependency auditing
Vulnerable dependencies are a common attack vector. Audit regularly:
pip install pip-audit
pip-audit
Run pip-audit in CI to catch known vulnerabilities before deployment. Subscribe to Django’s security mailing list for framework-level advisories.
Update Django promptly when security releases ship. Django’s security team follows responsible disclosure practices and provides clear upgrade guidance.
Django’s deployment checklist
Django includes a built-in deployment checker:
python manage.py check --deploy
This reports common security misconfigurations: missing HSTS, insecure cookies, DEBUG = True, and more. Fix every warning before deploying.
File upload security
- Validate file types by content, not just extension
- Set
FILE_UPLOAD_MAX_MEMORY_SIZEandDATA_UPLOAD_MAX_MEMORY_SIZE - Never serve uploaded files through Django in production
- Store uploads outside the web root
- Scan uploads for malware in high-risk applications
Rate limiting
Protect login endpoints and sensitive forms from brute-force attacks:
# Using django-ratelimit
from django_ratelimit.decorators import ratelimit
@ratelimit(key='ip', rate='5/m', method='POST', block=True)
def login_view(request):
...
Rate limiting on login prevents credential stuffing. Apply it to password reset and registration endpoints as well.
Frequently asked questions
Is Django secure by default?
Django provides strong security defaults, but configuration matters. An unconfigured production deployment with DEBUG = True and no HTTPS is not secure. Run check --deploy and follow this checklist.
How do I handle security for REST APIs? The same principles apply. Use token or session authentication, enforce HTTPS, validate input, parameterize queries, and set appropriate CORS headers. Django REST Framework provides additional security utilities.
Should I use a WAF (Web Application Firewall)? A WAF adds defense in depth but should not be your primary security layer. Fix vulnerabilities in code first. A WAF protects against common attack patterns and gives you time to patch.
How often should I update Django? Apply security releases immediately. Minor version updates (5.1 to 5.2) can wait for your next scheduled maintenance window, but do not delay more than a few weeks. Major version migrations should be planned as a project.