skip to content

Reviewing a startup's first production deploy of a Django app, which HTTPS and cookie settings do you require, and why roll out HSTS gradually?

level: seniorimportance: should knowfreq 50%

answer

  1. redirect, then remember
  2. cookies that never travel over HTTP
  3. the proxy decides what is secure
  4. a max-age browsers cache
  5. start small, then a year

basics

~10 s

Require SECURE_SSL_REDIRECT (or a proxy redirect), SESSION_COOKIE_SECURE and CSRF_COOKIE_SECURE, plus SECURE_PROXY_SSL_HEADER behind a TLS-terminating proxy. Enable HSTS with a small SECURE_HSTS_SECONDS first, because browsers cache the policy and a mistake cannot be recalled.

solid answer

~40 s

I want every request on HTTPS: `SECURE_SSL_REDIRECT = True` in `SecurityMiddleware`, or the proxy doing the redirect. If TLS ends at the proxy, Django sees plain HTTP, so `request.is_secure()` is false and the redirect loops until `SECURE_PROXY_SSL_HEADER` names the header the proxy sets — only if the proxy always overwrites it. Then `SESSION_COOKIE_SECURE` and `CSRF_COOKIE_SECURE` to `True`, both `False` by default; `SESSION_COOKIE_HTTPONLY` is already `True`. HSTS goes last and slowly: `SECURE_HSTS_SECONDS` defaults to `0`; Django's docs suggest testing with `3600` and raising to `31536000` once nothing breaks, because every browser that sees the header refuses plain HTTP for that long. `SECURE_HSTS_INCLUDE_SUBDOMAINS` only if every subdomain is HTTPS, and `SECURE_HSTS_PRELOAD` only as a deliberate, hard-to-reverse decision.

code

python · 10 lines
python
# settings/production.py
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')  # only if the proxy always sets it
SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True

# Phase 1: prove nothing breaks; raise to 31536000 once confirmed.
SECURE_HSTS_SECONDS = 3600
SECURE_HSTS_INCLUDE_SUBDOMAINS = False
SECURE_HSTS_PRELOAD = False

go deeper

for a junior

Know that production Django sites should redirect to HTTPS and mark the session and CSRF cookies as secure.

for a middle

Explain each setting's default, why SecurityMiddleware must be installed, and how a TLS-terminating proxy breaks is_secure() until SECURE_PROXY_SSL_HEADER is set.

for a senior

Plan the rollout order, including the HSTS trial value, and judge when SECURE_PROXY_SSL_HEADER is safe given how the proxy handles client headers.

for a principal

Own domain-wide policy: who approves includeSubDomains and preload, and how certificate operations are guaranteed before HSTS commits every subdomain.

## The review in one table A first production deploy usually arrives with `startproject` defaults. For HTTPS and cookies, the review comes down to these settings: | Setting | Django default | Launch value | Why | |---|---|---|---| | `SECURE_SSL_REDIRECT` | `False` | `True`, unless the proxy redirects | every HTTP request is sent to HTTPS | | `SECURE_PROXY_SSL_HEADER` | `None` | the proxy's header, if TLS ends at the proxy | lets `request.is_secure()` see HTTPS | | `SESSION_COOKIE_SECURE` | `False` | `True` | session cookie never sent over HTTP | | `CSRF_COOKIE_SECURE` | `False` | `True` | CSRF cookie never sent over HTTP | | `SESSION_COOKIE_HTTPONLY` | `True` | keep | scripts cannot read the session cookie | | `SECURE_HSTS_SECONDS` | `0` | `3600` first, then `31536000` | browsers refuse HTTP for that long | | `SECURE_HSTS_INCLUDE_SUBDOMAINS` | `False` | `True` only if all subdomains are HTTPS | policy covers subdomains | | `SECURE_HSTS_PRELOAD` | `False` | only by explicit decision | allows preload-list submission | Most of these are reported by `manage.py check --deploy` (W008, W012, W016, W004, W005, W021). All the `SECURE_*` headers are applied by `SecurityMiddleware`, which must be in `MIDDLEWARE` (W001 otherwise). ## The proxy question comes first In most deployments TLS is terminated by a reverse proxy or load balancer, and the proxy talks plain HTTP to the application server. Django then believes every request is insecure: 1. `SECURE_SSL_REDIRECT = True` redirects the request to HTTPS. 2. The browser follows, the proxy terminates TLS again, forwards plain HTTP, and Django redirects again — an infinite redirect loop. 3. HSTS headers never appear, because `SecurityMiddleware` adds them only to responses it considers secure. The fix is `SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')` or whichever header your proxy sets. It is safe **only** if the proxy always sets or strips that header on incoming requests; otherwise a client can send it and be treated as secure. ## Cookies The session and CSRF cookies default to being sent over plain HTTP too. With `SESSION_COOKIE_SECURE` and `CSRF_COOKIE_SECURE` on, the browser attaches them only to HTTPS requests, so a stray HTTP request — a mistyped link, a downgraded connection — does not leak the session. `SESSION_COOKIE_HTTPONLY` already defaults to `True`; `CSRF_COOKIE_HTTPONLY` defaults to `False` and turning it on offers little, because the CSRF token is also exposed to pages by design. ## Why HSTS goes last and slowly HTTP Strict Transport Security is a response header telling the browser: for the next *N* seconds, never use plain HTTP for this domain, and do not let the user click through certificate errors. Django sends it when `SECURE_HSTS_SECONDS` is non-zero. The risk is that **the browser, not the server, holds the policy**: - if some part of the site still needs HTTP, visitors who saw the header cannot reach it until the max-age expires; - if the certificate lapses, users cannot bypass the warning; - `includeSubDomains` extends this to every subdomain, including ones another team runs; - `preload` asks browsers to ship the policy built in, which is slow to undo. So the docs recommend a **small value such as 3600 seconds first**, confirming nothing breaks, then **31536000 (one year)**. The deploy check's own wording warns that enabling HSTS carelessly can cause serious, irreversible problems. ## A rollout order for the startup 1. Put `SecurityMiddleware` first in `MIDDLEWARE` and set `SECURE_PROXY_SSL_HEADER` if TLS ends at the proxy. 2. Turn on `SECURE_SSL_REDIRECT` (or the proxy redirect) and confirm there is no loop. 3. Turn on the secure cookie settings; users will need to sign in again over HTTPS. 4. Set `SECURE_HSTS_SECONDS = 3600`, watch for breakage, then raise it to a year. 5. Decide on subdomains and preload separately, with whoever owns the domain. ## What interviewers listen for The proxy and `is_secure()` interaction, secure cookie flags, and an HSTS plan that respects its irreversibility rather than pasting a one-year value on day one.

  • After setting SECURE_SSL_REDIRECT = True behind the load balancer, the site loops forever. Why, and what fixes it?
    TLS ends at the load balancer, which forwards plain HTTP, so `request.is_secure()` is false and `SecurityMiddleware` redirects every request to HTTPS again. Set `SECURE_PROXY_SSL_HEADER` to the header the load balancer adds, such as `('HTTP_X_FORWARDED_PROTO', 'https')`, provided it always overwrites that header from clients.
  • Why is SECURE_PROXY_SSL_HEADER dangerous to set when you are not behind a proxy that controls the header?
    Django then trusts a request header to decide whether the connection is secure. If clients can reach Django directly or the proxy passes their header through, anyone can send `X-Forwarded-Proto: https` over plain HTTP and be treated as secure, defeating the redirect and secure-only logic.

HSTS is like telling a courier service to deliver to your address only by armoured van for the next year. The instruction sits in the courier's records, not yours, so if one door still takes only ordinary post, you cannot cancel the order early — you wait until it expires. That is why you try it for an hour before signing up for a year.

saying these in an interview costs you the question

  • Set SECURE_HSTS_SECONDS to a year on launch day, it is the recommended value
  • Session cookies are HTTPS-only by default in Django
  • SECURE_PROXY_SSL_HEADER is safe to set on any deployment
  • HSTS can be switched off instantly by setting SECURE_HSTS_SECONDS back to 0
  • SECURE_HSTS_INCLUDE_SUBDOMAINS is always correct to enable