skip to content

In a Django project, why does WhiteNoiseMiddleware go directly after SecurityMiddleware, and what happens when it receives a static file request?

level: middleimportance: must knowfreq 45%

answer

  1. short-circuit near the top
  2. index built at startup
  3. security headers still wanted
  4. skip sessions, auth and CSRF

basics

~20 s

WhiteNoiseMiddleware answers any request whose path matches a file it indexed at startup and returns without calling the rest of the stack. Placing it right after SecurityMiddleware keeps HTTPS redirects and security headers on assets while skipping every other middleware.

solid answer

~40 s

When the process starts, `WhiteNoiseMiddleware` scans `STATIC_ROOT` and builds a dictionary from URL path to file. On each request it looks up `request.path_info`; on a hit it builds the file response itself and returns without calling `get_response`, so nothing below it runs, no URL resolution and no view. On a miss the request continues down the stack as usual. The WhiteNoise docs place it directly after `django.middleware.security.SecurityMiddleware` and before everything else. After, because `SecurityMiddleware`'s request phase can redirect HTTP to HTTPS and its response phase adds headers such as HSTS and `X-Content-Type-Options`, which assets should carry too. Before the rest, because session, CSRF, authentication and message middleware have no work to do for a stylesheet, and third-party middleware that logs, queries or rewrites responses would otherwise run for every asset.

code

python · 10 lines
python
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'whitenoise.middleware.WhiteNoiseMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

go deeper

for a junior

Recall the WhiteNoise docs' rule: directly after SecurityMiddleware, before all other middleware, and that it serves collected files from STATIC_ROOT.

for a middle

Explain the short-circuit: an index built at startup, a dictionary lookup on path_info, and a response returned without calling get_response, wrapped only by the layers above.

for a senior

Reason about what each layer above or below costs or changes for assets, including third-party middleware and the restart needed after files change.

for a principal

Decide when serving assets from the Python process is acceptable and when the traffic justifies moving them off it entirely behind a CDN or object store.

## What WhiteNoise is **WhiteNoise** is a third-party package that lets the Django process serve its own collected static files efficiently, so a deployment does not need a separate web server just for `/static/`. In a Django project it is installed as a middleware class, `whitenoise.middleware.WhiteNoiseMiddleware`, added to the `MIDDLEWARE` setting. ## What it does with a request 1. **At startup** the middleware's constructor reads settings: the URL prefix (the path part of `STATIC_URL`), `STATIC_ROOT`, and optionally `WHITENOISE_ROOT`. It walks those directories and builds an in-memory dictionary mapping each URL path to a file entry with precomputed headers. 2. **Per request** `__call__` looks up `request.path_info` in that dictionary. With `WHITENOISE_AUTOREFRESH` on (its default is the value of `DEBUG`) it searches the filesystem instead, which is meant for development only. 3. **On a hit** it returns a file response built from the precomputed headers and never calls `get_response`. Nothing further down the middleware list runs, the URLconf is not consulted and no view executes. 4. **On a miss** it calls `get_response(request)` and the request proceeds as normal. Because the index is built once, a file that appears in `STATIC_ROOT` after the process started is not served until the process restarts (unless autorefresh is on). ## Why directly after SecurityMiddleware Django's middleware form layers: request handling runs top to bottom, response handling bottom to top. A middleware that returns early is still wrapped by every layer **above** it. - `SecurityMiddleware` sits above WhiteNoise, so its request phase can still redirect plain HTTP to HTTPS when `SECURE_SSL_REDIRECT` is on, and its response phase still adds `Strict-Transport-Security` (on HTTPS requests, when `SECURE_HSTS_SECONDS` is set), `X-Content-Type-Options: nosniff`, `Referrer-Policy` and `Cross-Origin-Opener-Policy` to asset responses. - Everything else sits **below**, and is skipped for static hits. ## Why before everything else | Middleware below WhiteNoise | What it would do for an asset request if placed above | |---|---| | `SessionMiddleware` | attach a lazy session object that no asset needs | | `CommonMiddleware` | apply user-agent and `PREPEND_WWW` checks to every file | | `CsrfViewMiddleware` | read the CSRF cookie for a request no view will handle | | `AuthenticationMiddleware` | attach a lazy `request.user` nobody reads | | Third-party middleware | log, time, query the database or rewrite bodies per asset | The built-in ones are cheap because much of their work is lazy, but a page can pull dozens of assets, and project or third-party middleware is often not lazy at all. The WhiteNoise docs are explicit: place it directly after `SecurityMiddleware` (if you use it) and **above all other middleware**, and ignore third-party advice to put something else at the very top unless you understand the consequences. ## A correct stack ```python MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'whitenoise.middleware.WhiteNoiseMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', ] ``` ## Common mistakes - **Appending it at the end.** Static requests then pass through the whole stack before being answered, and any middleware above it that answers early in its request phase (a `PREPEND_WWW` redirect, a custom gate) can intercept assets. - **Putting it above `SecurityMiddleware`.** Assets lose the HTTPS redirect and the security headers. - **Expecting new files without a restart.** The index is built at startup in production. - **Using it for uploads.** It only knows files present at startup, and the WhiteNoise docs rule it out for user media.

  • A new CSS file is copied into STATIC_ROOT on a running server, and WhiteNoise returns 404 for it; why?
    With `WHITENOISE_AUTOREFRESH` off, the default when `DEBUG` is `False`, the middleware builds its URL-to-file index once in its constructor and serves only from that dictionary. A file added afterwards is unknown until the process restarts. Collect static files before the process starts; autorefresh is meant for development only.
  • What does WhiteNoise use as the URL prefix when STATIC_URL is a full CDN address?
    It uses only the path component of `STATIC_URL`, so `https://cdn.example.com/static/` still gives a prefix of `/static/` on the origin, with `FORCE_SCRIPT_NAME` stripped if set. `WHITENOISE_STATIC_PREFIX` overrides it for unusual set-ups such as a CDN that rewrites paths.

It is like a leaflet rack placed just inside a building's security desk: visitors still pass the guard, but anyone who only wants a leaflet takes one and leaves without queuing at reception.

saying these in an interview costs you the question

  • WhiteNoise should go at the end of MIDDLEWARE so views get the first chance.
  • Static requests still reach the URL resolver after WhiteNoise handles them.
  • Putting WhiteNoise above SecurityMiddleware is fine because assets need no HTTPS.
  • WhiteNoise rescans STATIC_ROOT on each request in production.
  • WhiteNoise needs a URL pattern in urls.py to serve files.