Skip to content

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:

  1. STATIC_ROOT - Collected static files (fastest)
  2. STATICFILES_DIRS - Additional configured directories
  3. 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:

python manage.py collectstatic
# 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.NONCE values 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_ROOT and STATICFILES_DIRS are 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:

python manage.py collectstatic

Then run Django-Bolt:

python manage.py runbolt --host 0.0.0.0 --port 8000 --processes 4

Static files are served from STATIC_ROOT with async I/O, ETag caching, and range request support.

Troubleshooting

Static files not found

  1. Verify STATIC_ROOT is set and contains files:

    python manage.py collectstatic
    ls -la $(python -c "from django.conf import settings; print(settings.STATIC_ROOT)")
    

  2. Check STATIC_URL matches your requests:

    # settings.py
    STATIC_URL = "/static/"  # Requests should be /static/css/style.css
    

  3. For development without collectstatic, ensure STATICFILES_DIRS is configured.

Admin styles missing

Ensure django.contrib.staticfiles is in INSTALLED_APPS and run collectstatic:

python manage.py collectstatic --noinput

CSP blocking resources

Check browser console for CSP violations. Adjust your CSP directives to allow required sources:

from django.utils.csp import CSP

SECURE_CSP = {
    "script-src": [CSP.SELF, "https://trusted-cdn.com"],
    "style-src": [CSP.SELF, CSP.UNSAFE_INLINE],  # Allow inline styles
}