Fix "Missing staticfiles manifest entry" in Django
Fix ValueError: Missing staticfiles manifest entry in Django production: collectstatic not run, wrong STATIC_ROOT, typos in static paths, missing apps, and build-time settings mismatches.
Problem statement
After a deploy, pages return 500 Internal Server Error and the logs show:
ValueError: Missing staticfiles manifest entry for 'css/site.css'
It often appears only with DEBUG = False, only in production, or only on pages that use a particular template. The error comes from ManifestStaticFilesStorage (or WhiteNoise's CompressedManifestStaticFilesStorage), which maps each static file name to a hashed name using a manifest file, staticfiles.json, written by collectstatic. When a template asks for a file that is not in the manifest, Django raises this error instead of rendering a broken link.
- Run
python manage.py collectstatic --noinputfor this release with production settings, and confirmSTATIC_ROOT/staticfiles.jsonexists. - Check the exact path in the error against the source file: spelling, case, and directory.
- Make sure the directory containing the file is in
STATICFILES_DIRSor inside an app inINSTALLED_APPS. - Make sure
collectstaticand the running app use the sameSTATIC_ROOTandSTORAGESsettings. - In Docker, run
collectstaticin the image build with settings that match runtime. - Only as a last resort, set
manifest_strict = Falseon a storage subclass to fall back to unhashed names.
Step-by-step solution
1. Confirm collectstatic ran for this release
ls -l /srv/myapp/current/staticfiles/staticfiles.json
python -c "import json; d=json.load(open('/srv/myapp/current/staticfiles/staticfiles.json')); print(len(d['paths']))"
If the file is missing or old, collectstatic did not run for the code that is live. Add it to the deploy after installing dependencies and before switching traffic:
python manage.py collectstatic --noinput
With release directories, each release needs its own run (or a shared STATIC_ROOT updated during the deploy).
2. Check whether the file exists in the manifest
python - <<'EOF'
import json
paths = json.load(open("staticfiles/staticfiles.json"))["paths"]
print("css/site.css" in paths)
print([p for p in paths if p.lower().endswith("site.css")])
EOF
If a similar name exists, the template reference is wrong. Linux filesystems are case-sensitive: CSS/Site.css works on macOS during development and fails on a Linux server.
3. Find where the file should come from
python manage.py findstatic css/site.css --verbosity 2
findstatic searches the same finders as collectstatic. If it finds nothing:
- The file is not committed (check
git ls-files | grep site.css); built assets such as compiled CSS are often ignored by Git and never reach the server. - Its directory is not in
STATICFILES_DIRS. - It belongs to an app that is not in
INSTALLED_APPSin production settings (for example an app only enabled in development settings).
If the file is generated by a build step (Tailwind, Vite, Sass), run that step before collectstatic in the deploy.
4. Compare settings between build and runtime
DJANGO_SETTINGS_MODULE=config.settings.production python -c "
import django; django.setup()
from django.conf import settings
print(settings.STATIC_ROOT)
print(settings.STORAGES['staticfiles'])
"
Run that with the settings used by collectstatic and with the settings used by Gunicorn. Common mismatches:
collectstaticran with a build settings module whoseSTATIC_ROOTpoints elsewhere.- The Docker image ran
collectstaticinto/app/staticfiles, but a volume mounted at runtime hides that directory with an empty or stale one. - Development settings use
StaticFilesStoragewhile production uses a manifest storage, so the problem never shows locally.
5. Fix Docker builds
COPY . .
RUN DJANGO_SETTINGS_MODULE=config.settings.production \
DJANGO_SECRET_KEY=build-only-not-secret \
python manage.py collectstatic --noinput
Settings needed only at runtime (database, real secrets) must not be required at import time for this to work. If your production settings read mandatory environment variables at import, provide harmless build values for the collectstatic step, or create a build settings module that shares the same STATIC_ROOT and STORAGES values.
Do not mount a volume over STATIC_ROOT unless something else populates it with the collected files.
6. References inside CSS
Manifest storage also rewrites url() references inside CSS files during collectstatic. If a CSS file references an image that does not exist, collectstatic fails with a similar ValueError naming the missing file and the CSS file that references it. Fix or remove the broken reference, or add the missing file.
7. Last resort: non-strict manifest
# config/storage.py
from whitenoise.storage import CompressedManifestStaticFilesStorage
class ForgivingManifestStorage(CompressedManifestStaticFilesStorage):
manifest_strict = False
With manifest_strict = False, a missing entry falls back to the unhashed name instead of raising. Pages render, but the file may 404 and will not get cache-busting. Use it only to stop an outage while you fix the real cause.
Verification check:
python manage.py findstatic css/site.css
curl -sI https://example.com/ | head -1
curl -s https://example.com/ | grep -o '/static/css/site\.[a-f0-9]*\.css'
The page should return 200 and reference the hashed file, which should itself return 200.
Explanation
With DEBUG = True, manifest storage returns unhashed names and never consults the manifest, which is why the problem hides in development. With DEBUG = False, every {% static %} call is a lookup in staticfiles.json. The manifest contains only what collectstatic found through the configured finders, using the configured settings, at the moment it ran. Any difference between that moment and the running app (a file added later, a different settings module, a hidden directory) produces a missing entry.
Edge cases / notes
Stale manifest with shared STATIC_ROOT
If several releases share one STATIC_ROOT, the manifest describes the most recent collectstatic. A rollback to older code usually still works because older files remain, but a rollback that reintroduces a deleted file does not; rerun collectstatic on rollback.
Tests
Test settings using manifest storage require collectstatic before tests. Use StaticFilesStorage in test settings unless you specifically test static files.
When to automate this
Make collectstatic a mandatory build step that fails the pipeline, and add a smoke test that loads a few key pages with DEBUG = False in CI. That catches missing manifest entries before deploy.
Next steps
- For errors during
collectstaticitself, see Fix collectstatic Errors in Django Deployments. - Understand hashed filenames in Long-Lived Caching for Django Static Files.
- Configure WhiteNoise correctly with How to Serve Django Static Files with WhiteNoise.
- For other DEBUG-only differences, see Django Works with DEBUG=True but Fails with DEBUG=False.
FAQ
Why does "Missing staticfiles manifest entry" only happen with DEBUG=False?
Manifest storage only uses the manifest when DEBUG is off. With DEBUG on, it returns the plain file name.
Where is the Django staticfiles manifest?
In STATIC_ROOT/staticfiles.json, written by collectstatic when a manifest storage backend is configured.
Should I set manifest_strict to False?
Only as a temporary measure. It hides missing files, which then 404 in the browser without cache-busting.
Why is the file found locally but missing in production?
It is often not committed to Git, generated by a build step that did not run in production, or referenced with different letter case.