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.

How the manifest lookup failsstatic tagcss/site.cssManifest storagereads staticfiles.jsonNo entry foundValueError500 responsewhole page failsThe manifest only knows files that collectstatic found; any other name raises ValueError.
  1. Run python manage.py collectstatic --noinput for this release with production settings, and confirm STATIC_ROOT/staticfiles.json exists.
  2. Check the exact path in the error against the source file: spelling, case, and directory.
  3. Make sure the directory containing the file is in STATICFILES_DIRS or inside an app in INSTALLED_APPS.
  4. Make sure collectstatic and the running app use the same STATIC_ROOT and STORAGES settings.
  5. In Docker, run collectstatic in the image build with settings that match runtime.
  6. Only as a last resort, set manifest_strict = False on 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
the manifest maps names to hashes{"paths": {"css/app.css": "css/app.4f1c9e2a.css","js/main.js": "js/main.b07d31aa.js"},"version": "1.1"}1231The name templates ask for.2The hashed file actually served.3No entry here = the error you see.It lives in STATIC_ROOT and is written by collectstaticdjango-deployment.com
grep the name in staticfiles.json to confirm in seconds.

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_APPS in 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'])
"
build and runtime must agreeSettings differbuild uses base settings✕an app or STATICFILES_DIRS missing✕runtime asks for files never collectedSame settings module✓DJANGO_SETTINGS_MODULE set for the build✓same INSTALLED_APPS✓manifest matches what runsOnly secrets may differ; anything affecting static files may notdjango-deployment.com
Placeholder secrets at build time are fine; different apps are not.

Run that with the settings used by collectstatic and with the settings used by Gunicorn. Common mismatches:

  • collectstatic ran with a build settings module whose STATIC_ROOT points elsewhere.
  • The Docker image ran collectstatic into /app/staticfiles, but a volume mounted at runtime hides that directory with an empty or stale one.
  • Development settings use StaticFilesStorage while 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
manifest_strict, comparedMissing entryUse whenmanifest_strict = True!ValueError: 500default: catches mistakesmanifest_strict = False✕unhashed URL, maybe 404last resort, temporarilyNon-strict turns a loud error into a silent broken assetdjango-deployment.com
Fix the collect step, then switch strict mode back on.

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

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.

Keep going