Skip to content
>_ITDITDWeb Security Platform

By framework

Django security — a production hardening reference

A Django production-hardening reference: a priority checklist plus DEBUG/ALLOWED_HOSTS, SECRET_KEY, pip CVEs, production security settings, authorization, injection, and SSRF. Defensive, no attack steps.

Published 2026-07-02 Updated 2026-07-02 6 min read

For: anyone running a Django app. No attack steps here — this is a working reference for hardening: a priority-ordered checklist, per-area guidance, and self-verification. For the cross-framework picture, see the security-by-framework hub.

Priority-ordered hardening checklist

Do this table top-down. P0 is a prerequisite, P1 is the most frequent source of incidents, P2 is ongoing operational hygiene.

P0 ── Prerequisite (do first)

DEBUG=False + ALLOWED_HOSTS / externalize SECRET_KEY / patch pip dependency CVEs fast

P1 ── Top source of incidents

Production security settings (SSL/HSTS/cookies) / authorization (owner scope)

P2 ── Operational hygiene

Injection and output / CSRF, sessions, admin / SSRF, uploads

Harden from the foundation up: P0 (prerequisite) → P1 (top source of incidents) → P2 (operational hygiene).
PriorityControlSpecifics (Django)
P0DEBUG/ALLOWED_HOSTSProduction DEBUG=False + ALLOWED_HOSTS set. Don't expose detailed errors
P0SECRET_KEYFrom the environment, not in code/repo. Rotate on leak
P0pip dependency CVEsMonitor with pip-audit / osv-scanner; judge by running version, patch fast
P1Production security settingsSECURE_SSL_REDIRECT, SECURE_HSTS_SECONDS, SESSION_COOKIE_SECURE, CSRF_COOKIE_SECURE, SECURE_CONTENT_TYPE_NOSNIFF
P1Explicit authorizationLogin required + permissions + filter(user=request.user) for owner scope
P2Injection/outputBind via the ORM. Avoid raw()/extra() interpolation. Don't pass input to mark_safe/`
P2CSRF/sessions/adminDon't disable CSRF (default). Restrict admin exposure
P2SSRF/uploadsAllowlist server-side fetches + block internal IPs. Validate uploads, store off the public surface

1. DEBUG and ALLOWED_HOSTS (P0)

  • Ensure DEBUG=False in production. On, the error page exposes settings, env vars, and stack traces, extractable via deliberate errors.
  • Set ALLOWED_HOSTS correctly to prevent operation under unexpected hosts. Stop detailed errors from showing externally.

2. SECRET_KEY (P0)

  • SECRET_KEY underpins signed cookies, sessions, CSRF tokens, and password resets. Load it from an environment variable or secret manager, not code/repo.
  • Rotate promptly if leaked. Keep it off public directories and DEBUG pages (→ keep secrets out of public directories).

3. pip dependency CVEs (P0)

4. Production security settings (P1)

Django configures much of its defense via SecurityMiddleware. Set these for production:

  • SECURE_SSL_REDIRECT (force HTTPS) · SECURE_HSTS_SECONDS (+ INCLUDE_SUBDOMAINS/PRELOAD)
  • SESSION_COOKIE_SECURE / CSRF_COOKIE_SECURE (HTTPS-only cookies)
  • SECURE_CONTENT_TYPE_NOSNIFF, etc.
  • Run manage.py check --deploy to mechanically surface gaps in these.

5. Authorization (P1 — the top source)

Common (dangerous)

  • login required, but no owner scope
  • querysets over everything, no ID filtering
  • relying on hidden URLs / hard-to-guess IDs
  • forgetting a permission check on some views

Correct

  • login required + explicit permission checks
  • reads too are owner-scoped (e.g. filter(user=request.user))
  • verified on every read/update/delete path
  • authorization built explicitly, not left to defaults

See what IDOR is. Authentication and authorization differ; authorize close to the data.

6. Injection and output (P2)

  • SQL: bind via the ORM. Don't build queries with raw()/extra() or string interpolation (→ what SQL injection is).
  • XSS: templates auto-escape by default. Don't pass user input to mark_safe/|safe (→ what XSS is).
  • Deserialization: don't load untrusted pickle (can lead to code execution).

7. CSRF, sessions, admin (P2)

  • Don't disable the default CSRF protection (→ what CSRF is).
  • Restrict admin exposure (access limits, URL change, multi-factor auth). Keep the clickjacking defense (X-Frame-Options default).
  • Apply secure/httponly/samesite to sessions and regenerate on login.

8. SSRF, uploads, headers (P2)

  • Server-side fetching of user-supplied URLs should restrict targets to an allowlist and block reaching internal IPs/metadata (→ what SSRF is).
  • Validate uploads (type/size) and store them off the public surface.
  • Check your own site's headers with the security headers checker.

Verify: is your Django actually hardened?

Building it isn't the end — it's done only once you've checked. These are defensive self-checks against your own environment.

1

Mechanically check for gaps

Run python manage.py check --deploy and confirm the warnings are resolved.
2

Production doesn't expose DEBUG

Trigger an error and confirm settings/env vars/stack aren't shown externally.
3

Authorization holds

In a test environment, request another user's resource ID and confirm it is denied (read/update/delete).
4

Secrets and dependencies

Confirm SECRET_KEY isn't in the repo, and that pip-audit/osv is clean.

This site's view: batteries included, but settings and authorization are on you

Django guards a lot by default, but production settings (DEBUG/SECRET_KEY/ALLOWED_HOSTS/SSL) and authorization are things you must get right per environment and per app. The incidents we keep seeing are less elaborate attacks than settings/operations patterns: "debug was open in production," "a secret was exposed," "there was no authorization." So the center of gravity is tightening production settings, keeping secrets private, and making authorization explicit. Building check --deploy into your process is the shortcut.

FAQ

QWhat should I do first to secure Django?
A

The three P0 items: (1) ensure DEBUG=False in production and set ALLOWED_HOSTS correctly; (2) load SECRET_KEY from the environment (not in code/repo) and rotate on leak; (3) machine-monitor pip dependency CVEs and patch fast, judging by the running version. Next, move to production security settings (SSL/HSTS/cookies) and authorization. manage.py check --deploy mechanically surfaces missing settings.

QWhat's dangerous about leaving DEBUG=True in production?
A

With DEBUG on, the error page can show detailed internals — settings, environment variables, stack traces. An attacker can trigger errors on purpose to extract them. In production, always set DEBUG=False and configure ALLOWED_HOSTS correctly. Also stop detailed errors from showing externally and review static/media serving for production.

QWhy is SECRET_KEY important?
A

SECRET_KEY underpins signed cookies and sessions, CSRF tokens, and password resets. A leak can lead to forging or tampering with those. Don't put it in code or the repo — load it from an environment variable or secret manager, and rotate promptly if it leaks. Also keep it from being exposed via a public directory or a DEBUG page.

QWhich Django production security settings should I check?
A

The SecurityMiddleware-related ones: SECURE_SSL_REDIRECT (force HTTPS), SECURE_HSTS_SECONDS (HSTS), SESSION_COOKIE_SECURE / CSRF_COOKIE_SECURE (secure cookies), SECURE_CONTENT_TYPE_NOSNIFF, and more, configured for production. Running manage.py check --deploy mechanically surfaces gaps in these.

QHow do I manage pip dependency vulnerabilities?
A

Machine-monitor known CVEs with pip-audit or osv-scanner and patch fast, judging by the running version. Keep Django and Python on supported versions and don't leave EOL versions in place. Dependency freshness is a more realistic factor in incidents than elaborate attacks.