Static Files¶
For production deployments, we recommend letting Nginx handle static files at the reverse proxy layer — this offloads static traffic entirely so Django-Bolt focuses on API requests. That said, Django-Bolt also serves static files directly from Rust using Actix-files, delivering high-performance static file serving without Python overhead — a solid option when you want a simpler setup without configuring static file handling in your reverse proxy.
Overview¶
Static files are served with:
- Streaming responses - Memory efficient for large files
- ETag and Last-Modified headers - Automatic caching support
- Conditional requests - 304 Not Modified responses
- Range requests - Resumable downloads
- Content-Type detection - Automatic MIME type handling
- Content Security Policy - Configurable CSP headers
Configuration¶
Django-Bolt reads your existing Django static files settings:
# settings.py
# URL prefix for static files
STATIC_URL = "/static/"
# Directory where collectstatic gathers files (primary lookup)
STATIC_ROOT = BASE_DIR / "staticfiles"
# Additional directories to search (optional)
STATICFILES_DIRS = [
BASE_DIR / "static",
]
File lookup order¶
When a static file is requested, Django-Bolt searches in this order:
- STATIC_ROOT - Collected static files (fastest)
- STATICFILES_DIRS - Additional configured directories
- Django staticfiles finders (debug mode only) - App-level static files (e.g., admin)
The first match is returned. Files in STATIC_ROOT take priority.
Finders disabled in production
For security, Django's staticfiles finders fallback is only enabled when DEBUG=True. In production, only files in STATIC_ROOT and STATICFILES_DIRS are served. Run collectstatic to copy app-level static files (like admin assets) into STATIC_ROOT.
Django admin integration¶
In development (DEBUG=True), Django admin static files are automatically served via Django's staticfiles finders — no extra configuration needed.
In production (DEBUG=False), you must run collectstatic so admin assets are copied into STATIC_ROOT:
# Admin works out of the box in development
INSTALLED_APPS = [
"django.contrib.admin",
"django.contrib.staticfiles",
# ...
]
Security¶
Directory traversal protection¶
Django-Bolt blocks directory traversal attacks:
- Paths containing
..are rejected - Symlink targets are verified to stay within allowed directories
- Only files (not directories) are served
Content Security Policy (CSP)¶
Django-Bolt applies CSP headers to all static file responses. This protects against XSS attacks in HTML files and ensures scripts/styles are loaded only from trusted sources.
Configure CSP using Django 6.0+'s native SECURE_CSP setting:
# settings.py
from django.utils.csp import CSP
SECURE_CSP = {
"default-src": [CSP.SELF],
"script-src": [CSP.SELF],
"style-src": [CSP.SELF, CSP.UNSAFE_INLINE],
"img-src": [CSP.SELF, "data:"],
}
You can also use raw strings:
SECURE_CSP = {
"default-src": ["'self'"],
"script-src": ["'self'"],
"style-src": ["'self'", "'unsafe-inline'"],
"img-src": ["'self'", "data:"],
}
CSP constants¶
Django provides constants via django.utils.csp.CSP:
| Constant | Value | Purpose |
|---|---|---|
CSP.SELF |
'self' |
Same origin only |
CSP.NONE |
'none' |
Block all resources |
CSP.UNSAFE_INLINE |
'unsafe-inline' |
Allow inline scripts/styles |
CSP.UNSAFE_EVAL |
'unsafe-eval' |
Allow eval() |
CSP.STRICT_DYNAMIC |
'strict-dynamic' |
Trust scripts loaded by trusted scripts |
How CSP works¶
- CSP configuration is read once at server startup
- The header is pre-built for performance (no per-request overhead)
CSP.NONCEvalues are automatically filtered out (static files cannot inject nonces)
Nonces not supported
CSP.NONCE requires per-request nonce injection which isn't possible for static files. Use CSP.NONCE only for dynamic responses served by your Django views.
Common CSP directives¶
| Directive | Purpose | Example |
|---|---|---|
default-src |
Default policy for all content | [CSP.SELF] |
script-src |
JavaScript sources | [CSP.SELF, "https://cdn.example.com"] |
style-src |
CSS sources | [CSP.SELF, CSP.UNSAFE_INLINE] |
img-src |
Image sources | [CSP.SELF, "data:", "https:"] |
font-src |
Font sources | [CSP.SELF, "https://fonts.gstatic.com"] |
connect-src |
XHR/WebSocket destinations | [CSP.SELF, "https://api.example.com"] |
frame-ancestors |
Who can embed this page | [CSP.NONE] |
upgrade-insecure-requests |
Upgrade HTTP to HTTPS | [] (boolean directive) |
block-all-mixed-content |
Block mixed HTTP/HTTPS | [] (boolean directive) |
Using with templates¶
The Django {% static %} template tag works as expected:
{% load static %}
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="{% static 'css/style.css' %}">
</head>
<body>
<img src="{% static 'img/logo.png' %}" alt="Logo">
<script src="{% static 'js/app.js' %}"></script>
</body>
</html>
Performance¶
Static file serving runs primarily in Rust:
- No Python GIL for configured directories - Files in
STATIC_ROOTandSTATICFILES_DIRSare served without touching Python - Django finders fallback (debug mode only) - App-level static files (like admin) require a Python call; disabled in production for security
- Async I/O - Non-blocking file reads via Actix
- Smart read modes - Sync reads for small files (<256KB), async for large files
- Efficient caching - Proper HTTP caching headers reduce server load
Production deployment¶
For production, collect static files (including admin assets) into STATIC_ROOT:
Then run Django-Bolt:
Static files are served from STATIC_ROOT with async I/O, ETag caching, and range request support.
Troubleshooting¶
Static files not found¶
-
Verify
STATIC_ROOTis set and contains files: -
Check
STATIC_URLmatches your requests: -
For development without collectstatic, ensure
STATICFILES_DIRSis configured.
Admin styles missing¶
Ensure django.contrib.staticfiles is in INSTALLED_APPS and run collectstatic:
CSP blocking resources¶
Check browser console for CSP violations. Adjust your CSP directives to allow required sources: