Settings Reference¶
Django-Bolt settings are configured in your Django settings.py file.
Bolt reads its settings at startup. A missing setting gives its default. A setting with the wrong type stops startup with ImproperlyConfigured. The error names the setting:
- A size or a count must be an
int, such as10 * 1024 * 1024orFileSize.MB_10. A str such as"10485760"is not an int. A bool is not an int. - A flag such as
CORS_ALLOW_CREDENTIALSmust be abool. DEBUGmust be aboolor anint, as Django reads it:DEBUG = 1is on, andDEBUG = 0is off. A str such as"False"stops startup, because Django reads a str that is not empty as on.- A list setting such as
CORS_ALLOWED_ORIGINSmust be a list of str. One str is not a list.
Convert a value that you read from an environment variable:
CORS settings¶
CORS_ALLOWED_ORIGINS¶
List of allowed origins for CORS.
CORS_ALLOWED_ORIGIN_REGEXES¶
List of regular expressions for allowed origins. An origin is allowed when it is in CORS_ALLOWED_ORIGINS or when a pattern matches it. A pattern can match a part of the origin, so start each pattern with ^ and end it with $. Bolt ignores an invalid pattern and writes a warning at startup. A route with @cors uses its own origins and patterns in place of CORS_ALLOWED_ORIGINS and these patterns.
CORS_ALLOW_ALL_ORIGINS¶
Allow all origins (development only).
CORS_ALLOW_CREDENTIALS¶
Allow credentials in CORS requests.
CORS_ALLOW_METHODS¶
Allowed HTTP methods for CORS.
CORS_ALLOW_HEADERS¶
Allowed headers in CORS requests. The default is ["Content-Type", "Authorization"].
CORS_EXPOSE_HEADERS¶
Headers exposed to the browser.
CORS_PREFLIGHT_MAX_AGE¶
Preflight cache duration in seconds. The default is 3600.
Rate limiting settings¶
BOLT_TRUSTED_PROXIES¶
Proxies that sit in front of Bolt, as addresses or CIDR blocks. The list is empty by default.
Bolt uses this list to find the canonical client address for
@rate_limit(key="ip") and request.META["REMOTE_ADDR"]:
- The list is empty. Bolt keys on the peer address of the connection and ignores every forwarding header.
- The peer is not in the list. Bolt keys on the peer address. That peer is not a declared proxy, so its headers prove nothing.
- The peer is in the list. Bolt reads
X-Forwarded-Forfrom the right and takes the first entry that is not a listed proxy. When every entry is a listed proxy, the client is inside your network and Bolt takes the leftmost entry. Without the header, Bolt falls back to the lastX-Real-IPline when it is a valid address, then the peer address. A malformed chain also falls back to the peer.
Bolt uses the same list for the request scheme. Only a peer in the list can set the scheme with X-Forwarded-Proto. Bolt reads the rightmost value, because the proxy closest to Bolt adds it. A value other than http or https is ignored. From any other peer, the scheme is the scheme of the connection. Bolt does not end TLS, so that scheme is http. The scheme sets these values:
request.build_absolute_uri()and the defaultrequest.META["SERVER_PORT"]on Bolt routes.- The
schemekey, and the port of theserverkey, of the ASGI scope ofmount_asgi()andmount_django(). Django readsscope["scheme"]forrequest.is_secure().
Django's SECURE_PROXY_SSL_HEADER still applies on top of this scheme, in Django code and in request.build_absolute_uri(), as in Django. Django middleware on a Bolt route reads the scheme only from SECURE_PROXY_SSL_HEADER, as before. That setting trusts the header from every peer, so set it only when the proxy removes the header from client requests.
The origin check of cookie authentication reads X-Forwarded-Proto from every peer. That check passes only one origin, so a value that a client sends never makes it weaker than the scheme of the connection. See Cross-site request forgery protection.
Both an address and a block are accepted. 127.0.0.1 matches one address. 10.0.0.0/8 matches the block. An invalid entry raises ImproperlyConfigured at startup.
Set this when you run behind a proxy
A client sends X-Forwarded-For itself, and a proxy appends to it. Without this setting Bolt ignores the header, so every caller behind the proxy shares one bucket. With the setting, Bolt separates them again and a client cannot pick its own bucket. Behind a proxy that ends TLS, Bolt also needs this setting to see https.
BOLT_RATE_LIMIT_IPV6_PREFIX¶
The prefix length of the network that a rate limit counts an IPv6 client on. The value is an int from 0 to 128. The default is 64.
A provider usually gives one client a /64 network or more. With a key of the full address, that client could send each request from a new address and get a new bucket. Thus @rate_limit(key="ip") counts an IPv6 client per /64 network. The address fallback of key="user" and key="api_key" does the same. An IPv4 client is counted per address.
Set 128 to count each IPv6 address. Do this when clients that are not related share one /64, for example virtual machines of a cloud provider. Set a smaller value, such as 56 or 48, to count a larger network as one client. The 429 log line names the network, for example 2001:db8:1:2::/64. request.META["REMOTE_ADDR"] keeps the full address. An invalid value raises ImproperlyConfigured at startup.
File upload settings¶
BOLT_MAX_UPLOAD_SIZE¶
Maximum file upload size in bytes. Requests exceeding this limit will be rejected with a 413 error before any per-view validation occurs.
You can also use the FileSize enum for readability:
Global vs Per-View Limits
BOLT_MAX_UPLOAD_SIZE is a global server limit. You cannot override it in individual views. If a file upload exceeds this value, the request is rejected before your view handler runs.
The max_size argument in the File() parameter is for per-view validation. It allows you to set a stricter limit for a specific endpoint, but it can never increase the global limit. For example:
# settings.py
BOLT_MAX_UPLOAD_SIZE = FileSize.MB_50 # 50 MB global limit
# In your view
file: Annotated[UploadFile, File(max_size=FileSize.MB_1)] # 1 MB per-view limit
If you set File(max_size=...) higher than BOLT_MAX_UPLOAD_SIZE, the global limit always takes precedence.
This distinction is important for security and resource management. Always set BOLT_MAX_UPLOAD_SIZE to the maximum file size your server should ever accept, and use File(max_size=...) for endpoint-specific needs.
Native route settings¶
BOLT_NATIVE_ROUTE_TIMEOUT¶
Maximum time (seconds) to wait for a native route handler to complete a request. At the limit, Bolt sends 504 Gateway Timeout.
Default: 30
The value must be more than 0. An int or a float is valid.
ASGI mount settings¶
BOLT_ASGI_MOUNT_TIMEOUT¶
Maximum time (seconds) to wait for a mounted ASGI app to complete.
Default: 30
The value must be more than 0. An int or a float is valid.
When exceeded, Django-Bolt returns 504 Gateway Timeout for the mounted request.
BOLT_MEMORY_SPOOL_THRESHOLD¶
Size threshold before file uploads are spooled to disk. Files smaller than this are kept in memory; larger files are written to a temporary file on disk.
Default: 1048576 (1 MB)
This setting controls memory usage during file uploads:
- Lower values reduce memory usage but increase disk I/O
- Higher values improve performance for medium-sized files but use more memory
When to adjust
- Set higher (e.g., 5-10 MB) if you frequently receive medium-sized files and have sufficient memory
- Set lower (e.g., 256 KB) on memory-constrained systems or when handling many concurrent uploads
Runtime environment variables¶
Bolt reads its DJANGO_BOLT_* environment variables at startup. An unset or empty variable gives the default. A value that is not valid stops startup with ImproperlyConfigured. The error names the variable and the value, for example DJANGO_BOLT_ORM_THREADS must be an int of 1 or more, got '0'.
DJANGO_BOLT_MAX_PARAM_LENGTH¶
Maximum allowed size for path/query/form parameter values, in bytes. Requests that exceed this limit are rejected with HTTP 422.
Default: 8192
Maximum: 1048576 (1 MB). A larger value stops startup, so a misconfiguration can never disable the limit.
The value must be an int from 1 to 1048576.
DJANGO_BOLT_ORM_THREADS¶
Number of threads in the ORM pool of each process. Django-Bolt evaluates the QuerySets that async handlers return on this pool. await request.auser() also loads the user on this pool. Django's async ORM API in async handlers without Django middleware, such as await Model.objects.aget(), acount() and async for, runs on this pool too. A route with Django middleware runs these calls on its request lane instead. The pool size is the number of queries in flight per process.
Default: 1 when every database is SQLite, otherwise 4.
Without a connection pool, each thread holds one database connection. With a pool, a thread holds a connection only while it has work. Increase the value when one free-threaded process runs many worker threads. See Size the database thread pools. The value must be an int of 1 or more.
DJANGO_BOLT_LANE_IDLE_SECONDS¶
Idle time, in seconds, after which a request lane closes its database connections and stops. Routes with Django middleware use request lanes. See Request lanes.
Default: 10. A fraction such as 0.5 is valid. The value must be a number more than 0.
DJANGO_BOLT_EXECUTOR_THREADS¶
Number of threads in the shared executor pool of each process. Blocking sync handlers and sync_to_thread calls run on this pool.
Default: CPU count + 4, maximum 32.
The value must be an int of 1 or more.
File serving settings¶
BOLT_ALLOWED_FILE_PATHS¶
Whitelist of directories for FileResponse.
The setting is a list or a tuple of absolute paths. Each path is a str or a Path. One str is not a list, so it stops startup with ImproperlyConfigured. runbolt reads the setting at startup. Under TestClient, the first FileResponse reads it. override_settings changes the limit for the next FileResponse. A relative path, such as "" or "media", also stops startup. None or an empty list sets no limit.
When set, FileResponse only serves files within these directories. For a path outside them, it raises ResponseFileNotAllowed, and Bolt answers 403. When the lookup of a path fails, Bolt follows its links, as realpath does, to the point where the lookup stops. Nothing exists below that point, so Bolt completes the path from there as text. When that path is outside these directories, Bolt answers 403, also when the path names no file. Thus a client cannot learn which regular files exist there, on Linux, macOS and Windows. A path can leave these directories with .. and come back into them. Such a path can still show whether a directory, or a link into these directories, exists outside them.
Static and media file serving¶
Django-Bolt serves static and media files from Rust based on your standard Django settings — STATIC_URL / STATIC_ROOT / STATICFILES_DIRS for static, and MEDIA_URL / MEDIA_ROOT for media. The scopes mount automatically when those settings are configured.
See Static Files and Media Files for full documentation.
BOLT_STATIC_MAX_AGE¶
Cache-Control max-age (seconds) for successful static responses. Emits Cache-Control: public, max-age=N (static assets are identical for every user, so shared caches can cache them).
Default: unset (no Cache-Control header)
BOLT_MEDIA_MAX_AGE¶
Cache-Control max-age (seconds) for successful media responses. Emits Cache-Control: private, max-age=N — private so shared caches don't serve one user's upload to another.
Default: unset (no Cache-Control header)
Validation
Both settings only emit a header on 2xx responses. Missing, non-integer, boolean, or negative values are ignored with a startup warning. STATIC_URL / MEDIA_URL must be literal prefixes — values containing {/} are refused at startup.
Authentication settings¶
Django-Bolt uses Django's SECRET_KEY for JWT signing by default.
Override per-backend:
BOLT_AUTHENTICATION_CLASSES¶
Default authentication backends applied to all endpoints.
Default: [] (no authentication)
BOLT_DEFAULT_PERMISSION_CLASSES¶
Default permission guards applied to all endpoints.
Default: [AllowAny()] (all endpoints publicly accessible)
See Global authentication for usage and examples.
Logging settings¶
Django-Bolt integrates with Django's logging system.
LOGGING = {
"version": 1,
"handlers": {
"console": {
"class": "logging.StreamHandler",
},
},
"loggers": {
"django_bolt": {
"handlers": ["console"],
"level": "INFO",
},
},
}
BOLT_LOG_QUEUE_SIZE¶
Django-Bolt logs through a bounded, non-blocking queue (a background thread
forwards records to the console). This sets the queue's maximum depth. When the
queue is full — e.g. during an extreme burst of error logging — new records are
dropped rather than accumulating in memory, preventing unbounded RAM growth under
load. Drops are counted and surfaced with a throttled notice to stderr.
Default: 10000
Django signals settings¶
BOLT_EMIT_SIGNALS¶
Enable Django request_started and request_finished signal emission.
Default: False
Django-Bolt disables signals by default for maximum performance. Enable this setting when:
- Using third-party packages that depend on request signals (e.g. django-debug-toolbar)
- Implementing custom signal receivers for request lifecycle events
CONN_MAX_AGE and CONN_HEALTH_CHECKS do not need signals. Bolt runs Django's connection check on the executor thread before each call when a database sets a positive CONN_MAX_AGE or CONN_HEALTH_CHECKS. With neither set, Bolt runs the check only after a call raises. A connection that the database closed then fails one request. The next request reconnects, and the other threads reconnect before their next call. See Database Connection Management.
See Django Signals for detailed documentation.
Dev reload settings¶
runbolt --dev watches two things and restarts the server when a .py or .html file changes in either:
- The project root (
BASE_DIR, falling back to the working directory), recursively — so newly created files, modules, and packages reload without restarting the dev server. Virtualenvs and common build/cache directories (.git,__pycache__,node_modules,target, …) are excluded. - The app's import graph — every module loaded by Django setup plus your discovered API modules and everything they transitively import, along with template directories and
manage.py. This covers code living outside the project root, such as apps onPYTHONPATHor editable-installed packages.
To watch extra locations outside both — for example a shared library next to the project that isn't imported at startup — pass --reload-dir (repeatable, same shape as uvicorn's flag):
Each entry is a directory (watched recursively) or a single file, resolved relative to the working directory. Entries that don't exist are skipped with a warning.
BOLT_DEV_FORCE_POLLING¶
Force the --dev auto-reloader to use file-system polling instead of native OS file events.
Default: False
Django-Bolt's dev reloader uses the notify crate's recommended watcher (inotify on Linux, FSEvents on macOS, ReadDirectoryChangesW on Windows). These backends are fast but rely on kernel events, which do not propagate across some bind-mount boundaries — most notably a Linux container running on a Windows host where the source tree is mounted from the host filesystem.
Enable polling when:
- Running
runbolt --devinside a Docker container on a Windows host with the project bind-mounted from the host - Working on a remote / network filesystem that does not deliver native change events
- Native file events appear to fire inconsistently for your editor or sync tool
Polling is checked every 500 ms. It uses more CPU than native events on large source trees but is the only reliable option when kernel events are unavailable.
This setting only affects runbolt --dev; production servers do not watch files.
runbolt command options¶
The runbolt management command accepts these options:
| Option | Default | Description |
|---|---|---|
--host |
0.0.0.0 |
Bind address: an IP address, an IPv6 address in brackets, or a host name. A host name binds its first IPv4 address, so localhost binds 127.0.0.1. A host name that does not resolve stops startup. |
--port |
8000 |
Bind port |
--workers |
1, or the CPU count on free-threaded Python |
Actix worker threads per process. Each thread runs Python handlers. |
--processes |
1 |
Number of processes |
--dev |
off | Enable auto-reload |
--no-admin |
off | Disable admin integration |
--backlog |
1024 |
Socket listen backlog |
--keep-alive |
OS default | HTTP keep-alive timeout |
--skip-checks |
off | Skip the Django system checks at startup |
At startup, runbolt runs the Django system checks and the migration check, like runserver. A check error stops startup. The unapplied-migration warning prints before the banner. In --dev mode, each reload runs the checks again. Multi-process mode runs them once, in the parent process. --skip-checks skips the system checks; the migration check still runs.
Examples¶
# Development with auto-reload
python manage.py runbolt --dev
# Production with scaling
python manage.py runbolt --processes 4 --workers 2
# Custom bind address
python manage.py runbolt --host 127.0.0.1 --port 3000
The dev reloader follows a Django-style source watch list: Python modules and HTML templates are reloaded automatically, while runtime files like logs, databases, and other generated artifacts do not trigger reloads.
OpenAPI settings¶
Configure via OpenAPIConfig in your api.py:
from django_bolt import BoltAPI
from django_bolt.openapi import OpenAPIConfig
api = BoltAPI(
openapi_config=OpenAPIConfig(
title="My API",
version="1.0.0",
description="API description",
enabled=True,
path="/docs",
)
)
Content Security Policy (CSP) settings¶
Django-Bolt applies CSP headers to static files served by the Rust server using Django 6.0+'s native SECURE_CSP setting.
SECURE_CSP¶
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:"],
}
See Static Files - Content Security Policy for full documentation.
Compression settings¶
Configure via CompressionConfig:
from django_bolt import BoltAPI, CompressionConfig
api = BoltAPI(
compression=CompressionConfig(
backend="brotli", # "brotli", "gzip", or "zstd"
minimum_size=1000, # Minimum size to compress (bytes)
gzip_fallback=True, # Fall back to gzip
)
)
All settings reference¶
| Setting | Type | Default | Description |
|---|---|---|---|
CORS_ALLOWED_ORIGINS |
list[str] |
[] |
Allowed CORS origins |
CORS_ALLOWED_ORIGIN_REGEXES |
list[str] |
[] |
Patterns for allowed CORS origins |
CORS_ALLOW_ALL_ORIGINS |
bool |
False |
Allow all origins |
CORS_ALLOW_CREDENTIALS |
bool |
False |
Allow credentials |
CORS_ALLOW_METHODS |
list[str] |
GET, POST, PUT, PATCH, DELETE, OPTIONS, QUERY |
Allowed methods |
CORS_ALLOW_HEADERS |
list[str] |
Content-Type, Authorization |
Allowed headers |
CORS_EXPOSE_HEADERS |
list[str] |
[] |
Exposed headers |
CORS_PREFLIGHT_MAX_AGE |
int |
3600 |
Preflight cache (seconds) |
BOLT_RATE_LIMIT_IPV6_PREFIX |
int |
64 |
Network prefix that a rate limit counts an IPv6 client on |
BOLT_MAX_UPLOAD_SIZE |
int |
1048576 |
Max upload size (bytes) |
BOLT_MEMORY_SPOOL_THRESHOLD |
int |
1048576 |
Memory threshold before disk spooling (bytes) |
BOLT_ALLOWED_FILE_PATHS |
list[str \| Path] |
None |
File serving whitelist |
BOLT_STATIC_MAX_AGE |
int |
None |
Cache-Control max-age for static (public) |
BOLT_MEDIA_MAX_AGE |
int |
None |
Cache-Control max-age for media (private) |
BOLT_LOG_QUEUE_SIZE |
int |
10000 |
Max depth of the non-blocking log queue (records dropped when full) |
BOLT_EMIT_SIGNALS |
bool |
False |
Enable Django request signals |
BOLT_DEV_FORCE_POLLING |
bool |
False |
Force --dev reloader to use polling instead of native file events |
SECURE_CSP |
dict |
None |
CSP directives for static files (Django 6.0+) |
BOLT_AUTHENTICATION_CLASSES |
list |
[] |
Default authentication backends |
BOLT_DEFAULT_PERMISSION_CLASSES |
list |
[AllowAny()] |
Default permission guards |
DJANGO_BOLT_MAX_PARAM_LENGTH |
int (env var) |
8192 |
Max path/query/form parameter size in bytes, from 1 to 1048576 (1 MB); requests over the limit return 422 |
DJANGO_BOLT_ORM_THREADS |
int (env var) |
1 (SQLite) or 4 |
Threads in the ORM pool of each process |
DJANGO_BOLT_LANE_IDLE_SECONDS |
float (env var) |
10 |
Idle time after which a request lane stops |
DJANGO_BOLT_EXECUTOR_THREADS |
int (env var) |
CPU count + 4 (max 32) |
Threads in the shared executor pool of each process |