Security Hardening
The production security checklist every Django deployment must pass — headers, cookies, secrets and safe queries.
Django is secure by default, but ‘by default’ assumes you turn the production switches on. Most Django breaches come not from framework bugs but from a misconfiguration a checklist would have caught. Here is that checklist, and the reasoning behind each item.
The non-negotiables
| Setting | Why |
|---|---|
DEBUG = False | DEBUG pages leak your settings, SQL and stack traces to attackers. |
ALLOWED_HOSTS set | Blocks Host-header poisoning and cache attacks. |
SECURE_SSL_REDIRECT | Forces HTTPS so credentials aren't sent in the clear. |
SESSION/CSRF_COOKIE_SECURE | Cookies only travel over HTTPS. |
SECURE_HSTS_SECONDS | Tells browsers to refuse plain HTTP for your domain. |
What Django protects automatically — if you don't opt out
- SQL injection — the ORM parameterises queries. The danger is
raw()andextra(): never format user input into those strings. - XSS — templates auto-escape. The danger is
mark_safeand|safeon untrusted data. - CSRF — protected by middleware and
{% csrf_token %}. Don't blanket-exempt views.
Run python manage.py check --deploy against your production settings; it audits most of this table for you.
Example
# --- settings/production.py: the hardening block ---
DEBUG = False
ALLOWED_HOSTS = ['example.com', 'www.example.com']
# Transport security
SECURE_SSL_REDIRECT = True
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') # behind a load balancer
SECURE_HSTS_SECONDS = 31536000
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True
# Cookies
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
SESSION_COOKIE_HTTPONLY = True
CSRF_COOKIE_SAMESITE = 'Lax'
# Content / clickjacking
SECURE_CONTENT_TYPE_NOSNIFF = True
X_FRAME_OPTIONS = 'DENY'
CSRF_TRUSTED_ORIGINS = ['https://example.com']
# --- Safe vs unsafe data access ---
from django.db import connection
# UNSAFE: string interpolation = SQL injection.
# User.objects.raw(f"SELECT * FROM auth_user WHERE username = '{name}'")
# SAFE: parameterised — the driver escapes the value.
def find_users(name):
return list(User.objects.raw(
'SELECT * FROM auth_user WHERE username = %s', [name]
))
def report_rows(status):
with connection.cursor() as cur:
cur.execute('SELECT id, total FROM orders WHERE status = %s', [status])
return cur.fetchall()
# --- Safe HTML from user content ---
from django.utils.html import format_html
def user_badge(display_name):
# format_html escapes display_name even if it contains <script>.
return format_html('<span class="tag">{}</span>', display_name)When to use it
- A developer runs django-axes to automatically lock accounts after a configurable number of failed login attempts.
- A team rotates the Django SECRET_KEY on every deployment using an environment variable to invalidate old sessions.
- A developer uses Content Security Policy headers via django-csp to prevent XSS attacks from injected scripts.
More examples
Production security settings checklist
A set of production Django settings that harden the app against common web vulnerabilities.
SECURE_SSL_REDIRECT = True
SECURE_HSTS_SECONDS = 31536000
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
CSRF_COOKIE_SECURE = True
SESSION_COOKIE_SECURE = True
SESSION_COOKIE_HTTPONLY = True
X_FRAME_OPTIONS = "DENY"
SECURE_CONTENT_TYPE_NOSNIFF = Truedjango-axes for brute-force protection
Installs django-axes to track failed login attempts and lock accounts after 5 failures.
# pip install django-axes
# settings.py
INSTALLED_APPS += ["axes"]
MIDDLEWARE += ["axes.middleware.AxesMiddleware"]
AUTHENTICATION_BACKENDS = [
"axes.backends.AxesStandaloneBackend",
"django.contrib.auth.backends.ModelBackend",
]
AXES_FAILURE_LIMIT = 5 # lock after 5 failed attemptsContent Security Policy header
Configures a Content Security Policy that restricts resource origins to prevent XSS injection.
# pip install django-csp
MIDDLEWARE = ["csp.middleware.CSPMiddleware", ...]
CSP_DEFAULT_SRC = ("'self'",)
CSP_SCRIPT_SRC = ("'self'", "https://cdn.jsdelivr.net")
CSP_STYLE_SRC = ("'self'", "https://fonts.googleapis.com")
CSP_IMG_SRC = ("'self'", "data:", "https:")
Discussion