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

SettingWhy
DEBUG = FalseDEBUG pages leak your settings, SQL and stack traces to attackers.
ALLOWED_HOSTS setBlocks Host-header poisoning and cache attacks.
SECURE_SSL_REDIRECTForces HTTPS so credentials aren't sent in the clear.
SESSION/CSRF_COOKIE_SECURECookies only travel over HTTPS.
SECURE_HSTS_SECONDSTells 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() and extra(): never format user input into those strings.
  • XSS — templates auto-escape. The danger is mark_safe and |safe on 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

Example · python
# --- 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.

Example · python
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 = True

django-axes for brute-force protection

Installs django-axes to track failed login attempts and lock accounts after 5 failures.

Example · python
# 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 attempts

Content Security Policy header

Configures a Content Security Policy that restricts resource origins to prevent XSS injection.

Example · python
# 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

  • Be the first to comment on this lesson.
Security Hardening — Django | SoundsCode