# WhiteNoise: static files served by the Python process itself

> WSGI middleware that lets a Python app hand out its own compressed static files with correct cache headers, so a small service can ship as one unit instead of a process plus an nginx plus an object store.

**evansd/whitenoise** — Radically simplified static file serving for Python web apps

- Repository: https://github.com/evansd/whitenoise
- Website: https://whitenoise.readthedocs.io
- Stars: 2,760 · Forks: 156
- Language: Python
- License: MIT
- Published: 2026-10-06 · Updated: 2026-10-06 · Language: en
- Canonical page: https://hysenlabs.com/projects/evansd-whitenoise

## The problem is a second service, not a missing feature

The README states the goal in its opening line: a couple of lines of configuration lets your web app serve its own static files, making it a self-contained unit that can be deployed anywhere without relying on nginx, Amazon S3 or any other external service. The parenthetical about Heroku, OpenShift and other PaaS providers is the real motivation. On those platforms there may be no nginx to configure and no bucket credentials to hold, so the usual arrangement of a web process plus a separate static file service is not available in the first place.

That reframes what the project is. It is not trying to be a faster web server than nginx. It is trying to remove a deployment dependency from the smallest possible unit of work, and to do the two things that hand-rolled static file serving usually gets wrong.

The package description in `pyproject.toml` is more precise than the repository blurb: Radically simplified static file serving for WSGI applications. That middlewares classification is the honest summary. It sits between your application and the server, and it serves the files the application declines to serve.

## Compression and cache headers, which is the real feature

The README's list of what it takes care of is short and specific. Serving compressed content in gzip and Brotli formats, handling `Accept-Encoding` and `Vary` headers correctly. And setting far-future cache headers on content which will not change.

Those two lines are the engineering content of the project. Compression without correct negotiation is worse than none, because a client that does not advertise support still receives the compressed body, or the `Vary` header is missing and a shared cache serves the wrong representation to the next visitor. Cache headers are the other half: hashed asset filenames let you set an expiry measured in years, and a cache header is only safe if the filename changes when the content does. A middleware can set that policy once, for every static file, instead of leaving it to a bucket policy or a CDN rule.

Brotli is not built in, and the packaging is explicit about that. It is an optional extra:

```toml
optional-dependencies.brotli = [
  "brotli",
]
```

So a deployment that wants Brotli has to ask for it, which is a good default for a library that is otherwise installed and forgotten.

## Any WSGI app, with extra shortcuts for Django

WhiteNoise works with any WSGI-compatible application. That is the general case and it is what the package description leads with.

The Django support is the reason most people meet the project, though, and the README describes it as special auto-configuration features for Django. In practice that means the middleware can find the static directories, hashed filenames and settings-driven file layout a Django project already has, rather than asking for a new configuration block that restates what Django knows. The repository carries exactly one topic tag, `django`, which tells you where the maintainers think the demand is.

The packaging backs that up with pinned test dependencies rather than a loose upper bound. Django 5.2 is pinned for Python 3.10, and later Django versions are gated on newer interpreters:

```toml
django52 = [ "django>=5.2a1,<6; python_version>='3.10'" ]
django60 = [ "django>=6a1,<6.1; python_version>='3.12'" ]
```

That pattern is the interesting part. Each Django release line is tested against the interpreter versions it actually supports, which is a maintenance cost the maintainers took on rather than leaving to users.

## What the packaging says about supported versions

Three lines of `pyproject.toml` answer most of the questions a team would otherwise ask in an issue tracker.

```toml
[project]
name = "whitenoise"
version = "6.12.0"
requires-python = ">=3.10"
```

Python 3.10 is the floor, and the classifier list runs from 3.10 through 3.15, so the project tracks new interpreter releases as they appear rather than declaring compatibility after the fact. The classifiers also say CPython only, Operating System Independent, and `Typing :: Typed`, the last of which is a public promise that the package ships type information.

Two maintainers are listed, Adam Johnson and David Evans, with David Evans as the author. The development status classifier is `Development Status :: 5 - Production/Stable`, which is a self-declaration the rest of the repository is consistent with: the repository has no GitHub releases at all, so versioning happens through PyPI and the changelog instead, and `CHANGELOG.rst` in the root is the record to read when you upgrade. Version 6.12.0 is the current number in the file. The last push was on 2026-08-18, and the licence is MIT.

## A repository built by uv, tox and pre-commit

The repository layout shows a project with its tooling pinned rather than improvised. The build backend is `uv_build`, requiring `uv-build>=0.12.1,<0.13`, and `uv.lock` is committed, so the development environment is reproducible. `tox.ini` sits alongside it for running the matrix.

There is a `.pre-commit-config.yaml`, and a `.typos.toml` for spell checking, plus `.editorconfig` and `.gitattributes`. A coverage badge in the README reads 96%, and the test dependency group explains how that number is produced: `coverage[toml]`, `django`, `pytest>=9`, `pytest-randomly` and `requests`. The random ordering plugin is the detail worth noting, since a suite that only passes in one order is a suite that will fail on someone else's machine. Documentation is built with Sphinx and the Furo theme, configured through `.readthedocs.yaml`, and the published site is whitenoise.readthedocs.io.

`SECURITY.rst` in the root is another sign of a mature project: there is a documented place to report a vulnerability, which is what you want from middleware that sits in front of every response.

## Where a CDN still wins, and where the README defers

The honest limitation is throughput, and the project does not hide it. The README anticipates the objection directly, asking whether serving static files with Python is horribly inefficient and whether you should be using Amazon S3, then sends you to an Infrequently Asked Questions page in the documentation rather than arguing in the README. That is the right place for the answer, and it also tells you the project regards this as its hardest question.

The design answer is visible in the README anyway. WhiteNoise is designed to work nicely with a CDN for high-traffic sites so you do not have to sacrifice performance to benefit from simplicity. So the intended deployment for a large site is not WhiteNoise instead of a CDN but WhiteNoise in front of your app and the CDN in front of that, which changes what the middleware is for: origin configuration and correctness rather than last-mile delivery.

The comparison to make is therefore with doing nothing. Handing static files from Django's own staticfiles view or a WSGI app's route works, and it works badly in the specific ways WhiteNoise fixes: no compression negotiation, no long-lived cache headers, no precompressed files. What you are giving up by not using nginx or S3 is the ability to serve those files without occupying a Python worker. If your traffic is measured in requests per second rather than in deployable units, this is the wrong tool.

## Conclusion

WhiteNoise earns its place in the small and unglamorous case: a Django or WSGI app that has static assets, a single box to deploy onto, and no appetite for running a separate web server or an object store alongside it. Compression and cache headers are handled correctly rather than approximately, which is the part you would otherwise get wrong. The cost is that Python is now serving bytes it could delegate, so a site with real traffic belongs in front of a CDN, and the README says as much by design. Two facts from the packaging are worth checking before you adopt it: the current version is 6.12.0 and requires Python 3.10 or later, and the Django versions it is tested against are pinned per interpreter in the project's own dependency groups. Read the Infrequently Asked Questions page in its documentation before arguing with anyone about whether Python is fast enough.

## FAQ

### What does WhiteNoise do for a Python web app?

It adds WSGI middleware that serves the app's own static files, so a service can be deployed as a self-contained unit without nginx, Amazon S3 or another external service. The README describes it as a couple of lines of configuration.

### Does WhiteNoise compress static files and set cache headers?

Yes. The README lists serving compressed content in gzip and Brotli formats with correct `Accept-Encoding` and `Vary` handling, plus far-future cache headers on content that will not change. Brotli needs the optional dependency, declared in `pyproject.toml` as the `brotli` extra.

### Which Python and Django versions does WhiteNoise support?

`pyproject.toml` sets `requires-python` to 3.10 or later, and the classifiers cover CPython from 3.10 to 3.15. Django is listed against 5.2, 6.0 and 6.1, and the test dependency groups pin each release line to the interpreter versions that line supports. The current version in the file is 6.12.0.

### Can WhiteNoise be used together with a CDN?

That is the intended arrangement for a high-traffic site. The README says WhiteNoise is designed to work nicely with a CDN so you do not have to sacrifice performance for simplicity, meaning the CDN handles delivery and the middleware handles the origin.

## Sources

- [evansd/whitenoise on GitHub](https://github.com/evansd/whitenoise)
- [Issues](https://github.com/evansd/whitenoise/issues)
- [License: MIT](https://github.com/evansd/whitenoise/blob/main/LICENSE)
- [Project website](https://whitenoise.readthedocs.io)
- [README](https://github.com/evansd/whitenoise/blob/main/README.md)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/evansd-whitenoise
