Django Static Files Production Checklist

A production checklist for Django static files: STATIC_ROOT and collectstatic in the build, manifest storage for cache busting, serving with WhiteNoise, Nginx, or a CDN, cache headers, compression, frontend bundles, and admin assets.

Problem statement

Static files are the most common first-deploy failure in Django: unstyled admin, 404s for CSS, stale JavaScript after a deploy, or slow pages because assets are uncompressed and uncached. They are also easy to get permanently right with a few decisions: where files are collected, how filenames change when content changes, who serves them, and what cache headers they carry. This checklist walks through each.

Static files pipelineBuildassets, collectstaticHashed namesmanifest storageServeWhiteNoise, Nginx, CDNCache foreverimmutable headersCollect at build time, hash names, serve efficiently, and cache aggressively.
  1. Set STATIC_URL and an absolute STATIC_ROOT separate from source STATICFILES_DIRS.
  2. Run frontend builds, then collectstatic --noinput, at build or deploy time.
  3. Use ManifestStaticFilesStorage or WhiteNoise's CompressedManifestStaticFilesStorage for hashed filenames.
  4. Serve with WhiteNoise, Nginx, or a CDN; never with DEBUG = True.
  5. Send Cache-Control: public, max-age=31536000, immutable for hashed files.
  6. Compress text assets (gzip and Brotli) and confirm admin assets load.

Checklist

Settings

  • STATIC_URL = "/static/" (or a CDN URL).
  • STATIC_ROOT = BASE_DIR / "staticfiles", not inside STATICFILES_DIRS, ignored by Git.
  • STORAGES["staticfiles"] uses a manifest storage backend.
  • Templates use {% static %}, never hard-coded /static/... paths.

Build and deploy

  • Frontend assets (Vite, Tailwind, webpack) built before collectstatic, with output inside a STATICFILES_DIRS path.
  • collectstatic --noinput runs in the Docker build or deploy script, not at container start.
  • Build fails if a referenced file is missing (manifest storage raises during collectstatic or rendering).
who serves /static/NeedsFitsWhiteNoisemiddleware + storagePaaS, containersNginx aliaslocation block, permissionsVPS with NginxCDN in frontorigin with cache headersglobal trafficS3 + CloudFrontstorage backend, collectstatic uploadserverless, large assetsAll options rely on hashed filenames for safe long-term cachingdjango-deployment.com
Pick the simplest option for your platform.

Serving and caching

  • Hashed files served with a one-year, immutable cache header (WhiteNoise does this by default).
  • Unhashed files (for example robots.txt, favicons at fixed paths) have short cache lifetimes.
  • Compression: WhiteNoise pre-compresses at collectstatic; Nginx uses gzip_static or on-the-fly gzip; CDNs compress automatically.
  • CORS headers present for fonts if served from another domain.

Verification

  • curl -I https://example.com/static/admin/css/base.css returns 200 with cache headers.
  • A changed CSS file gets a new hashed name after deploy; pages reference the new name.
  • Browser dev tools show static files compressed and served from cache on reload.
  • DEBUG = False locally (with runserver --insecure or WhiteNoise) to catch manifest errors before deploy.
static files that never breakSTATIC_ROOT separate and absolutegit-ignored outputBuild assets, then collectstaticin the build stepManifest storage for hashed namescache bustingServed by WhiteNoise, Nginx, or CDNnever DEBUGImmutable cache headers, compressionfast repeat visitsGet these right once and static files stop being a deploy riskdjango-deployment.com
Test with DEBUG off before every first deploy.

Explanation

Django separates static files' source locations from their production location: collectstatic gathers files from apps and STATICFILES_DIRS into STATIC_ROOT. Manifest storage adds a content hash to each filename and rewrites references in CSS, so a changed file gets a new URL and can be cached indefinitely. Serving is then a matter of mapping STATIC_URL to STATIC_ROOT efficiently, through middleware, a web server, or a CDN. Most failures come from skipping one of these steps or running with DEBUG assumptions in production.

Edge cases / notes

  • Missing manifest entry errors mean a template references a file that collectstatic did not produce; fix the path or build step.
  • Source maps referenced in CSS or JS must exist or be stripped, or manifest storage fails.
  • Multiple servers each need the same collected files (bake them into the image or use a CDN/S3).
  • Media files are not static files; they need separate storage and serving.

Next steps

FAQ

Why are Django static files not loading in production?

Usually collectstatic did not run, STATIC_ROOT is not served, or DEBUG is off with no WhiteNoise or Nginx configured.

Should collectstatic run at container start?

No. Run it at build time so containers start fast and identically.

What is manifest storage?

A storage backend that adds content hashes to filenames, enabling long-term caching with automatic cache busting.

Do I need a CDN for static files?

Not necessarily. WhiteNoise or Nginx with proper cache headers is fine; a CDN helps for global or heavy traffic.

Keep going