Chapter 2: Django – Installing WhiteNoise
Installing and configuring WhiteNoise (the simplest, most popular way to serve static files in production when you don’t have a separate nginx/Apache server yet)
Many people reach the deployment stage, run collectstatic, see 404 errors on CSS/JS/images in production, and spend hours googling “Django static files not working on Railway / Render / Heroku / Fly.io”.
WhiteNoise solves this in ~5 minutes — no nginx config, no extra server, no headaches.
Today I’m going to teach you exactly how to install and set up WhiteNoise — step by step — like I’m sitting next to you, typing commands, editing files, and testing together.
We will:
- Install WhiteNoise
- Configure it correctly in settings.py
- Test in development (it works there too)
- Prepare for production (Railway, Render, Heroku, etc.)
- Understand when WhiteNoise is enough and when you eventually need nginx
Let’s do this properly.
Step 1 – Why WhiteNoise? (Quick Real Talk)
In development (runserver): Django serves static files automatically — no problem.
In production (real server):
- Django does NOT serve static files (security + performance reasons)
- You need a web server (nginx, Apache) or middleware to serve them
WhiteNoise is a Django middleware that does the job of nginx for static files:
- Very fast (serves directly from Python process)
- Supports compression (gzip/brotli)
- Handles caching headers (far-future expires)
- Works perfectly on Railway, Render, Fly.io, Heroku, PythonAnywhere, DigitalOcean App Platform, etc.
- Zero extra server config
→ 90%+ of small-to-medium Django apps in 2026 use WhiteNoise for static files (and sometimes even media files with extra config).
Step 2 – Install WhiteNoise
In your activated virtual environment:
|
0 1 2 3 4 5 6 |
pip install whitenoise |
Check it installed:
|
0 1 2 3 4 5 6 |
pip show whitenoise |
→ You should see version ~6.0.x or higher (2026 current is around 6.1–6.2)
Step 3 – Configure settings.py (The Most Important Part)
Open mysite/settings.py
-
Add ‘whitenoise.middleware.WhiteNoiseMiddleware’ to MIDDLEWARE
Important: Put it right after SecurityMiddleware and before everything else.
Python0123456789101112131415MIDDLEWARE = ['django.middleware.security.SecurityMiddleware','whitenoise.middleware.WhiteNoiseMiddleware', # ← add this line here'django.contrib.sessions.middleware.SessionMiddleware','django.middleware.common.CommonMiddleware','django.middleware.csrf.CsrfViewMiddleware','django.contrib.auth.middleware.AuthenticationMiddleware','django.contrib.messages.middleware.MessageMiddleware','django.middleware.clickjacking.XFrameOptionsMiddleware',] -
Add these static settings (if not already there):
Python0123456789101112131415161718# Static files (CSS, JavaScript, Images)STATIC_URL = '/static/'# Where collectstatic puts filesSTATIC_ROOT = BASE_DIR / 'staticfiles'# Extra locations (your project static/ folder)STATICFILES_DIRS = [BASE_DIR / 'static',]# WhiteNoise settings – very important for productionSTATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'Explanation of CompressedManifestStaticFilesStorage:
- Compresses files (gzip/brotli)
- Adds content hash to filenames (cache busting)
- Serves far-future expires headers
→ This is the recommended storage backend for WhiteNoise in production.
Step 4: Make Sure Your Static Files Are Ready
(If you followed previous lessons you already have some)
Quick check:
- static/css/global.css
- polls/static/polls/css/polls.css
- static/images/logo.png
- {% load static %} in base.html
- Links like <link href=”{% static ‘css/global.css’ %}”>
Step 5: Run collectstatic (Even in Development – Test It)
|
0 1 2 3 4 5 6 |
python manage.py collectstatic --noinput |
You should see:
|
0 1 2 3 4 5 6 |
X static files copied to '/path/to/your/project/staticfiles'. |
→ This simulates production — WhiteNoise will serve from staticfiles/ in real deployment.
Step 6: Test in Development (WhiteNoise Works Here Too!)
Restart server:
|
0 1 2 3 4 5 6 |
python manage.py runserver |
Open browser → visit /polls/ or /
Check:
- Logo appears
- CSS styles applied (colors, fonts, shadows)
- Browser dev tools (F12 → Network) → see global.css, polls.css, logo.png with 200 OK
- Console → if you added JS, see your log message
If still 404:
- Forgot STATICFILES_STORAGE = ‘whitenoise.storage.CompressedManifestStaticFilesStorage’
- Forgot to run collectstatic
- Path mismatch in {% static ‘css/global.css’ %}
Step 7: Production Deployment Checklist (When You’re Ready)
- Set DEBUG = False in settings.py (or environment variable)
- Set ALLOWED_HOSTS = [‘yourdomain.com’, ‘.railway.app’, …]
- Run python manage.py collectstatic –noinput during deploy
- Use a platform that supports Python + persistent filesystem (Railway, Render, Fly.io, Heroku with buildpack, etc.)
- WhiteNoise handles /static/ automatically
No nginx needed for static files — WhiteNoise does it.
Step 8: Real Example – Add a Favicon
Create static/images/favicon.ico (or .png)
In base.html <head>:
|
0 1 2 3 4 5 6 |
<link rel="icon" href="{% static 'images/favicon.ico' %}" type="image/x-icon"> |
→ Your site now has a proper favicon.
Your Quick Practice Task (Do This Right Now)
- Install whitenoise if not already
- Add middleware and STATICFILES_STORAGE to settings.py
- Create or verify static/css/global.css with some style
- Add <link href=”{% static ‘css/global.css’ %}”> to base.html
- Run python manage.py collectstatic
- Restart server → check if styles apply
- (Optional) Add a small JS file → include it → see console log
Tell me what feels next:
- “Done! Now show me how to use Tailwind CSS locally or via CDN”
- “How to serve user-uploaded images (MEDIA files) in production?”
- “What is the difference between WhiteNoise and Whitenoise with nginx?”
- “Got 404 even after collectstatic – here’s the URL/error”
- Or finally ready for Django Forms + Voting + POST + F() + results page
You now have production-ready static file serving with WhiteNoise — one of the biggest deployment pain points is now solved forever.
You’re doing really well — let’s keep building! 🚀🇮🇳
