Filter concepts by levelShowing all levels.

Django · Settings and Configuration

Security settings

Concepts
2

SECRET_KEY, DEBUG, host validation, cookie security, and the HTTPS/HSTS/reverse-proxy settings that only matter once real traffic is involved.

This section

Cookies and host validation

The settings that guard against a forged Host header, a leaked signing key, or a stolen session cookie.

Advertisement

HTTPS and reverse proxies

Forcing HTTPS, committing to it via HSTS, and the one setting that requires trusting infrastructure outside Django.

HTTPS redirect, HSTS, and proxy settings

coreadvanced

SECURE_SSL_REDIRECT forces every HTTP request to HTTPS; SECURE_HSTS_SECONDS tells browsers to skip HTTP entirely for future visits; SECURE_PROXY_SSL_HEADER tells Django to trust a proxy header for "was this HTTPS" — and is dangerous if the proxy doesn't strip that header from untrusted clients first.

Think of it as

SECURE_SSL_REDIRECT is a doorman turning away anyone who didn't arrive over HTTPS. SECURE_HSTS_SECONDS goes further — it hands returning visitors' browsers a standing instruction ("never even try HTTP here again") for the given duration. SECURE_PROXY_SSL_HEADER is different in kind: it doesn't enforce anything, it tells Django to believe a header the proxy sets — which is only safe if the proxy guarantees no outside client can set that same header itself.

python
# only if the reverse proxy is verified to strip this header from clients
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")

What we're doing: Roll out HSTS safely — starting with a short duration before committing to the full year-long, preload-eligible configuration.

settings/production.pypython
SECURE_HSTS_SECONDS = 3600  # 1 hour — confirm nothing breaks first

# once confirmed working over several deploys:
# SECURE_HSTS_SECONDS = 31536000  # 1 year
# SECURE_HSTS_INCLUDE_SUBDOMAINS = True
# SECURE_HSTS_PRELOAD = True
1
A short duration limits the damage window if HTTPS turns out to be misconfigured — browsers that already received the header will refuse plain HTTP for that long, no way to undo it early.

Why this works: SECURE_HSTS_SECONDS is a one-way commitment for however many seconds it specifies — once a browser receives the header, it will not attempt HTTP again until that duration elapses, even if the site's HTTPS setup breaks in the meantime. Starting short and raising it only after confirming HTTPS works reliably avoids locking out real visitors from a misconfigured production rollout.

Setting SECURE_PROXY_SSL_HEADER without the proxy stripping client-supplied X-Forwarded-Proto

Wrong

python
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
# but the reverse proxy passes through any X-Forwarded-Proto
# a client sends, instead of overwriting it

Better

python
# nginx.conf (or equivalent) — always overwrite, never pass through
# proxy_set_header X-Forwarded-Proto $scheme;

# settings.py — now safe, since the proxy guarantees the header's value
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")

What you see: request.is_secure() returns True for a request that was actually plain HTTP end-to-end, if a client simply sends its own X-Forwarded-Proto: https header — silently defeating CSRF cookie security and any code branching on is_secure().

Why: Django trusts SECURE_PROXY_SSL_HEADER completely once set — it has no way to tell a proxy-set value of X-Forwarded-Proto from a client-forged one. The setting is only safe when the reverse proxy is configured to unconditionally overwrite that header on every request it forwards, never merely add to whatever a client already sent.

Enforcement vs. trust

SECURE_SSL_REDIRECT / HSTS

  • +Django itself enforces HTTPS
  • +HSTS tells browsers to skip HTTP next time
  • +A one-way commitment for its duration

SECURE_PROXY_SSL_HEADER

  • Django trusts a header, enforces nothing
  • Safe only if the proxy strictly overwrites it
  • Wrong config lets a client fake "was HTTPS"
  • SECURE_SSL_REDIRECT / HSTS
    • Django itself enforces HTTPS
    • HSTS tells browsers to skip HTTP next time
    • A one-way commitment for its duration
  • SECURE_PROXY_SSL_HEADER
    • Django trusts a header, enforces nothing
    • Safe only if the proxy strictly overwrites it
    • Wrong config lets a client fake "was HTTPS"

HTTPS redirect, HSTS, and proxy settings

HTTPS redirect, HSTS, and proxy settings
SettingDefaultControls
SECURE_SSL_REDIRECTFalseredirects every HTTP request to HTTPS when True
SECURE_HSTS_SECONDS0seconds browsers should remember to only use HTTPS
SECURE_HSTS_INCLUDE_SUBDOMAINSFalseextends the HSTS policy to all subdomains
SECURE_HSTS_PRELOADFalseallows submission to browsers' built-in HSTS preload list
SECURE_PROXY_SSL_HEADERNonewhich proxy header/value Django trusts as "this was HTTPS"

Together

python
SECURE_SSL_REDIRECT = True
SECURE_HSTS_SECONDS = 3600  # start short; raise once confirmed
SECURE_HSTS_INCLUDE_SUBDOMAINS = True

Remember: SECURE_SSL_REDIRECT/HSTS settings enforce HTTPS going forward; SECURE_PROXY_SSL_HEADER is different — it makes Django trust a header, safe only if the proxy strictly overwrites it for every request.

See also: cookie and host security settings · sessions

Advertisement