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 maintenance.
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 ── Ongoing maintenance
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, which an attacker can read by triggering errors on purpose. - 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: many safe defaults, but settings and authorization are your responsibility
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 focus 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
Sources and scope
These primary references support the settings, scope or remediation discussed above; links were checked on October 23, 2026. Consult their version and configuration conditions. Checklist priorities and lessons are this site's guidance, not a full-article revalidation or a safety guarantee.