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.
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
| Priority | Control | Specifics (Django) |
|---|---|---|
| P0 | DEBUG/ALLOWED_HOSTS | Production DEBUG=False + ALLOWED_HOSTS set. Don't expose detailed errors |
| P0 | SECRET_KEY | From the environment, not in code/repo. Rotate on leak |
| P0 | pip dependency CVEs | Monitor with pip-audit / osv-scanner; judge by running version, patch fast |
| P1 | Production security settings | SECURE_SSL_REDIRECT, SECURE_HSTS_SECONDS, SESSION_COOKIE_SECURE, CSRF_COOKIE_SECURE, SECURE_CONTENT_TYPE_NOSNIFF |
| P1 | Explicit authorization | Login required + permissions + filter(user=request.user) for owner scope |
| P2 | Injection/output | Bind via the ORM. Avoid raw()/extra() interpolation. Don't pass input to mark_safe/` |
| P2 | CSRF/sessions/admin | Don't disable CSRF (default). Restrict admin exposure |
| P2 | SSRF/uploads | Allowlist server-side fetches + block internal IPs. Validate uploads, store off the public surface |
1. DEBUG and ALLOWED_HOSTS (P0)
- Ensure
DEBUG=Falsein production. On, the error page exposes settings, env vars, and stack traces, extractable via deliberate errors. - Set
ALLOWED_HOSTScorrectly to prevent operation under unexpected hosts. Stop detailed errors from showing externally.
2. SECRET_KEY (P0)
SECRET_KEYunderpins 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)
- Machine-monitor known CVEs with pip-audit or osv-scanner, judge by the running version, and patch fast (→ monitoring dependency CVEs · the vulnerability-response playbook).
- Keep Django and Python on supported versions and don't leave EOL versions in place.
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 --deployto 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-Optionsdefault). - 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.
Mechanically check for gaps
python manage.py check --deploy and confirm the warnings are resolved.Production doesn't expose DEBUG
Authorization holds
Secrets and dependencies
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.
Read next
- Hub: security by framework · Laravel security (similar production-DEBUG / secret-exposure)
- Practice: the vulnerability-response playbook · monitoring dependency CVEs · keep secrets out of public directories
- Glossary: what IDOR is · SQL injection · XSS · CSRF · SSRF
- Tools: security headers checker
FAQ
QWhat should I do first to secure Django?
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?
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?
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?
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?
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.