Skip to main content
Back to blog
April 28, 2026|8 min read|Antoine Duno

Django Security Checklist 2026: CSRF, HSTS, CSP & Deployment Hardening

Django security is built into the framework — but only if you flip the right switches. This checklist covers every critical setting from DEBUG=False to Content Security Policy.

ZeriFlow Team

942 words

Django Security Checklist 2026: CSRF, HSTS, CSP & Deployment Hardening

Django security comes with a powerful set of built-in protections, but the framework trusts you to enable them correctly for production. Miss a setting and you could be exposing your application to SQL injection, session hijacking, or clickjacking — even on a well-written codebase.

Start by running a free automated scan with ZeriFlow — it checks your live site across 80+ security controls and gives you an instant baseline.


1. Core Production Settings: DEBUG, Hosts, and Secret Key

The most critical Django security mistake is deploying with DEBUG=True. It leaks full stack traces, settings values, and SQL queries to any visitor who triggers an error.

settings/production.py:

python
import os
from pathlib import Path

DEBUG = False

SECRET_KEY = os.environ['DJANGO_SECRET_KEY']  # Never hardcode

ALLOWED_HOSTS = [
    'yourdomain.com',
    'www.yourdomain.com',
]

# Prevent sensitive data in error emails
ADMINS = [('Security Team', 'security@yourdomain.com')]
MANAGERS = ADMINS

Generate a strong secret key with:

python
python -c 'from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())'

Store it in your environment or a secrets manager (AWS Secrets Manager, HashiCorp Vault) — never in version control.


2. CSRF Protection

Django ships with CSRF middleware enabled by default. Ensure it is present and never disabled in production.

settings.py:

python
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',  # MUST be present
    # ...
]

In templates, always use the csrf_token tag:

html
<form method='post'>
  {% csrf_token %}
  <!-- form fields -->
</form>

For AJAX requests, include the token in headers:

javascript
const csrfToken = document.cookie
  .split('; ')
  .find(row => row.startsWith('csrftoken='))
  .split('=')[1];

fetch('/api/endpoint/', {
  method: 'POST',
  headers: { 'X-CSRFToken': csrfToken },
  body: JSON.stringify(data),
});

If you use Django REST Framework, apply SessionAuthentication which enforces CSRF checks automatically for browser clients.


3. HTTPS: SECURE_SSL_REDIRECT and HSTS

Always serve Django over HTTPS. Use SecurityMiddleware to enforce it at the application layer.

python
# Force HTTPS redirect
SECURE_SSL_REDIRECT = True

# HSTS: tell browsers to only use HTTPS for 1 year
SECURE_HSTS_SECONDS = 31536000
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True  # Submit to browser preload lists

# Cookie security
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
SESSION_COOKIE_HTTPONLY = True
SESSION_COOKIE_SAMESITE = 'Lax'

# Prevent clickjacking
X_FRAME_OPTIONS = 'DENY'

# Prevent MIME-type sniffing
SECURE_CONTENT_TYPE_NOSNIFF = True

# Enable XSS filter in older browsers
SECURE_BROWSER_XSS_FILTER = True

IMPORTANT: Only enable SECURE_SSL_REDIRECT after you have a valid TLS certificate. Use Let's Encrypt (Certbot) for free certificates.


4. Content Security Policy

Django does not ship a built-in CSP header, but django-csp makes it straightforward.

bash
pip install django-csp
python
MIDDLEWARE = [
    # Add after SecurityMiddleware
    'csp.middleware.CSPMiddleware',
    # ...
]

CSP_DEFAULT_SRC = ("'self'",)
CSP_SCRIPT_SRC  = ("'self'",)
CSP_STYLE_SRC   = ("'self'", 'https://fonts.googleapis.com')
CSP_FONT_SRC    = ("'self'", 'https://fonts.gstatic.com')
CSP_IMG_SRC     = ("'self'", 'data:', 'https:')
CSP_CONNECT_SRC = ("'self'",)

# Use report-only mode first to detect violations without breaking the site
CSP_REPORT_ONLY = True
CSP_REPORT_URI  = '/csp-report/'

Once you have confirmed no legitimate resources are blocked, switch CSP_REPORT_ONLY = False.


5. Password Validators and Authentication Hardening

Django provides a built-in password validation framework. Use all available validators.

python
AUTH_PASSWORD_VALIDATORS = [
    {'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator'},
    {'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
     'OPTIONS': {'min_length': 12}},
    {'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator'},
    {'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator'},
]

# Upgrade hash algorithm to Argon2 (install django[argon2])
PASSWORD_HASHERS = [
    'django.contrib.auth.hashers.Argon2PasswordHasher',
    'django.contrib.auth.hashers.PBKDF2PasswordHasher',  # Fallback for migration
]

# Limit login attempts — use django-axes
AUTHENTICATION_BACKENDS = [
    'axes.backends.AxesStandaloneBackend',
    'django.contrib.auth.backends.ModelBackend',
]

AXES_FAILURE_LIMIT = 5
AXES_COOLOFF_TIME  = 1  # hours
AXES_LOCKOUT_TEMPLATE = 'lockout.html'

6. Dependency Auditing and Django Deployment Checklist

Run Django's built-in security check before every deployment:

bash
python manage.py check --deploy

This command flags any setting that does not meet Django's security recommendations. Treat all warnings as errors in CI.

For dependency auditing:

bash
pip install pip-audit
pip-audit

Also run:

bash
pip install safety
safety check -r requirements.txt

Integrate both commands into your CI pipeline. ZeriFlow complements these by scanning your live site headers, TLS configuration, and exposed endpoints after each deploy.


FAQ

### Q: Does Django protect against SQL injection automatically? A: Yes — the ORM uses parameterized queries internally. However, if you use raw() or extra() with string interpolation, you bypass these protections. Always use the params argument when writing raw SQL.

### Q: What does SECURE_HSTS_PRELOAD do? A: It adds your domain to browser preload lists, meaning browsers will refuse any non-HTTPS connection to your domain even on the very first visit — before they ever see your HSTS header. Only enable this once you are 100% committed to HTTPS everywhere.

### Q: Should I use django-allauth or build my own auth? A: Use django-allauth or a similar battle-tested library. Authentication is hard to get right, and rolling your own introduces subtle bugs around session fixation, timing attacks, and password reset flows.

### Q: How do I rotate the SECRET_KEY without logging everyone out? A: Django 4.1+ supports SECRET_KEY_FALLBACKS — a list of old keys used for verification but not signing. Rotate by moving the current key to fallbacks and generating a new primary key.

### Q: Is Django's admin panel safe to expose publicly? A: Only behind additional protections. Move it to a non-standard URL, restrict by IP at the web server level, enforce 2FA, and consider disabling it entirely in production if not needed.


Conclusion

Django's security posture in 2026 is excellent out of the box — but 'out of the box' means development mode. Production requires flipping a precise set of switches: DEBUG=False, CSRF middleware, SECURE_SSL_REDIRECT, HSTS, CSP, and strong password hashing.

Use manage.py check --deploy and pip-audit in your CI pipeline, and back it up with a live scan. Run a free ZeriFlow security scan to verify your headers, TLS config, and exposed endpoints from the outside — the same perspective an attacker would have.

Ready to check your site?

Run a free security scan in 30 seconds.

Related articles

Keep reading