Home / Blog / Django Security Checklist: From Settings to Production
Software Development

Django Security Checklist: From Settings to Production

AJAjish Stephen August 8, 2026 7 min read
Django Security Checklist: From Settings to Production

Django's defaults are genuinely good — better than most frameworks out of the box. But "good defaults" doesn't mean "secure by accident." Most Django security incidents I've seen trace back to a handful of settings that were never revisited after local development. Here's the checklist I actually run before any Django project goes live.

Run the built-in deployment check first

Before touching anything manually, let Django tell you what it already knows is wrong:

pythonmanage.py check --deploy

This flags common issues like DEBUG=True, missing HSTS settings, and an insecure SECRET_KEY — treat every warning as something to actually fix, not just acknowledge.

The production settings block

A reasonable baseline for settings.py in production:

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

SECURE_SSL_REDIRECT= True
SECURE_HSTS_SECONDS= 31536000
SECURE_HSTS_INCLUDE_SUBDOMAINS= True
SECURE_HSTS_PRELOAD= True

SESSION_COOKIE_SECURE= True
CSRF_COOKIE_SECURE= True
SESSION_COOKIE_HTTPONLY= True

X_FRAME_OPTIONS= 'DENY'
SECURE_CONTENT_TYPE_NOSNIFF= True

SESSION_COOKIE_SECURE and CSRF_COOKIE_SECURE ensure these cookies are only ever sent over HTTPS — without them, a session cookie could leak over an accidental plain HTTP connection.

SECRET_KEY doesn't belong in your codebase

SECRET_KEY signs sessions, password reset tokens, and CSRF tokens — if it leaks, all of that can be forged. Pull it from the environment instead of hardcoding it:

importos

SECRET_KEY= os.environ['DJANGO_SECRET_KEY']

Using os.environ[...] instead of os.environ.get(...) is deliberate — it raises a clear error at startup if the variable is missing, rather than silently falling back to an insecure default.

Stay inside the ORM for SQL safety

Django's ORM parameterizes queries automatically. The risk reappears the moment raw SQL is built with string formatting:

# ✗ Vulnerable — f-string interpolation into raw SQL
User.objects.raw(f"SELECT * FROM users WHERE email = '{email}'")

# ✓ Safe — parameterized
User.objects.raw("SELECT * FROM users WHERE email = %s", [email])

# ✓ Better — use the ORM directly
User.objects.filter(email=email)

A pre-launch checklist worth keeping

manage.py check --deploy returns no warnings
DEBUG is False and confirmed by actually visiting a broken URL in the deployed environment
ALLOWED_HOSTS is an explicit list, never a wildcard
Dependencies checked for known CVEs before every deploy, ideally as part of CI

Want a real security review of your Django application before it goes live, or after it's already been running for a while? This is part of what I cover in DevOps services engagements alongside infrastructure hardening.

Common questions

What does Django's check --deploy command actually do?
It scans your settings against a known list of production security concerns and warns about issues like DEBUG being enabled or missing HSTS headers. It is not a full security audit, but it catches the most common configuration mistakes in seconds.
Is Django's ORM safe from SQL injection by default?
Yes, as long as you use the ORM's query methods rather than raw SQL. The moment you drop into raw() with string-formatted user input, that protection disappears.
Why does SECRET_KEY matter so much?
It signs session cookies, password reset tokens, and CSRF tokens. If it leaks, an attacker can forge valid signed data, including session cookies for arbitrary users.
Do I need DEBUG = False even on a small internal project?
Yes, always in production, regardless of project size. DEBUG = True exposes full stack traces and settings to anyone who triggers an error.
Is your Django app actually production-ready?
I review real Django applications for security gaps before and after launch.
Explore DevOps Services →
© Copyright 2024 Ajish Stephen