WhiteNoise vs Nginx for Django Static Files

Compare serving Django static files with WhiteNoise inside the app versus Nginx or a CDN: setup, compression and cache headers, performance, containers and PaaS, media files, and the setup that suits each deployment.

Problem statement

In production, Django does not serve static files itself, so something else must: WhiteNoise (middleware that serves them from within the Django process), Nginx (reading files from disk), or a CDN (caching them at the edge). Advice conflicts: "never serve static files from Python" versus "WhiteNoise is fine". Both statements have context. With hashed filenames and far-future cache headers, most requests for static files never reach your server at all after the first visit, which changes the calculation.

Who serves /static/?Browserrequests assetCDN or cacheusually answersOriginWhiteNoise or NginxHashed namescache for a yearWith hashed names and long cache headers, the origin serves each file rarely.
  1. WhiteNoise for PaaS, containers, and simple setups: no proxy configuration, compression and immutable cache headers built in, works everywhere Django runs.
  2. Nginx when you already run it on the same host: very efficient, keeps Python workers free, and handles media files too.
  3. A CDN in front of either for global audiences or heavy traffic.
  4. Use CompressedManifestStaticFilesStorage (WhiteNoise) or ManifestStaticFilesStorage so filenames are hashed and cacheable forever.
  5. Media (user uploads) are different: never serve them with WhiteNoise; use Nginx, X-Accel-Redirect, or object storage.

Comparison

Setup

WhiteNoise: add the middleware right after SecurityMiddleware, set the storage backend, run collectstatic. Nginx: a location /static/ { alias ...; } block, correct permissions, and cache headers you add yourself. WhiteNoise's setup is identical on a VPS, Docker, Heroku, Render, or Cloud Run; Nginx requires a proxy you control.

Performance

Nginx serves files with sendfile from the kernel and is faster per request. WhiteNoise serves files from Python, using a worker for each request it handles. In practice, hashed filenames with Cache-Control: max-age=31536000, immutable mean browsers and CDNs request each file once per version, so the origin load is small. WhiteNoise also serves pre-compressed Brotli and gzip variants generated at collectstatic time.

serving /static/WhiteNoiseNginxSetup✓middleware + storagelocation block, permissionsPer-request costuses a Python worker✓kernel sendfileCompression✓precompressed br/gzipgzip_static, brotli moduleCache headers✓immutable by defaultconfigure yourselfWorks on PaaS✓yesonly if you run NginxWith hashed names and a CDN, the origin choice matters much lessdjango-deployment.com
Pick the simplest option for your platform, then add a CDN if traffic demands it.

Containers and PaaS

In containers, WhiteNoise keeps the image self-contained: static files are collected at build time and served by the app container, with no shared volume between app and proxy. With Nginx in a separate container, you need a shared volume or a second image that contains the static files. On PaaS without a proxy layer, WhiteNoise (or a CDN/object storage) is the practical choice.

With a CDN

Put Cloudflare, CloudFront, or another CDN in front of the site and both options become origin servers that are rarely hit. WHITENOISE_STATIC_PREFIX or STATIC_URL pointing at the CDN hostname works with either.

Media files are different

User uploads are not collected or hashed, can be large, and often need permission checks. Serve them with Nginx (alias or X-Accel-Redirect for private files) or from object storage. WhiteNoise intentionally serves only collected static files.

which static setup?How is the app deployed?PaaS or single containerWhiteNoiseself-contained imageVPS with Nginx alreadyNginx aliasworkers stay freeglobal or heavy trafficCDN in fronteither origin worksHashed filenames and long cache headers matter more than the serverdjango-deployment.com
Whatever serves the files, use manifest storage for cache busting.

Explanation

Static files are immutable build artefacts: CSS, JavaScript, fonts, and images produced by collectstatic. Once filenames include a content hash, they can be cached indefinitely by browsers and CDNs, and a new deploy simply references new filenames. That property makes the origin's efficiency less important than its correctness: correct headers, compression, and availability. WhiteNoise packages those correct behaviours inside Django; Nginx provides them with more raw efficiency when you already operate it.

Edge cases / notes

  • Admin static files are served the same way; missing admin CSS usually means collectstatic or serving is misconfigured.
  • Vite or webpack bundles with their own hashes work with both; avoid double hashing by collecting the built files.
  • HTTP/2 and HTTP/3 come from the front proxy or CDN, not WhiteNoise.
  • WhiteNoise with Nginx is also fine: Nginx proxies everything to Gunicorn and WhiteNoise serves static; simpler config at a small CPU cost.

Next steps

FAQ

Is WhiteNoise good enough for production?

Yes, especially with hashed filenames, long cache headers, and ideally a CDN in front.

Is Nginx faster than WhiteNoise?

Per request, yes. With proper caching, few requests reach either, so the difference rarely matters.

Can WhiteNoise serve media files?

No. Serve user uploads with Nginx, X-Accel-Redirect, or object storage.

Do I need both WhiteNoise and Nginx?

No. Use one to serve static files; Nginx can still proxy to Django while WhiteNoise serves them.

Keep going