settings.py
Django’s configuration is a Python module, which is its strength and its trap. It can compute values, import things and branch — so the discipline is entirely yours. As of 2026-08 the current release is Django 6.1, with 5.2 as the LTS.
The layout that survives contact with production
A single settings.py with if DEBUG: branches is the thing to move away
from. Split by environment, share a base:
config/settings/
base.py everything common
dev.py from .base import *
prod.py from .base import *
test.py from .base import *# manage.py / wsgi.py pick one via the environment.
os.environ.setdefault(
"DJANGO_SETTINGS_MODULE", "config.settings.dev"
)The property that matters: production settings are a file you can read, not
a set of conditionals you have to evaluate in your head. When something is
wrong in production you open prod.py and see the actual values.
Secrets come from the environment, and are validated
import os
def required(name: str) -> str:
try:
return os.environ[name]
except KeyError:
raise ImproperlyConfigured(f"{name} is not set")
SECRET_KEY = required("DJANGO_SECRET_KEY")
DATABASES = {"default": dj_database_url.parse(
required("DATABASE_URL"), conn_max_age=600,
)}required is the whole idea: a missing variable crashes at boot, where the
deploy pipeline sees it, rather than at the first request that happens to touch
that code path at 3am.
Gotcha:
os.environ.get("DEBUG", False)is always truthy in production, because the environment gives you the string"False". Parse booleans explicitly —os.environ.get("DEBUG", "") == "1"— or use a settings library that types them.
The production settings that are not optional
DEBUG = False
ALLOWED_HOSTS = required("ALLOWED_HOSTS").split(",")
SECURE_SSL_REDIRECT = True
SECURE_HSTS_SECONDS = 31_536_000
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
CSRF_TRUSTED_ORIGINS = ["https://app.example.com"]DEBUG = True in production is the classic incident: the error page renders a
full traceback, local variables, and the settings module — including anything
you did not think of as a secret. ALLOWED_HOSTS is the Host-header defence,
and Django refuses to start without it once DEBUG is off.
You do not have to remember this list. Django will tell you:
python manage.py check --deploy --settings=config.settings.prodRun it in CI against the production settings module. It is a security review that costs one line of pipeline.
What changed recently
| Setting | Status |
|---|---|
USE_L10N |
removed in 5.0 — always on |
USE_TZ |
defaults to True since 5.0 |
DEFAULT_AUTO_FIELD |
must be set; BigAutoField |
STORAGES |
replaced DEFAULT_FILE_STORAGE in 4.2 |
Naming USE_L10N or the old storage settings in an interview dates you the
same way any stale API does.
Settings you will actually tune
CONN_MAX_AGE— persistent database connections.0reconnects per request, which is a measurable cost under load and a disaster behind pgbouncer in transaction mode. See Databases.CACHES— Redis for sessions and the cache, sized deliberately.LOGGING— structured JSON to stdout in a container, not a file path.STORAGES— S3 or a CDN for media in production;staticfilesis a separate backend from default file storage.
Interview angle 6
- “How do you manage settings across environments?” - one base module with per-environment files that import it, values from the environment, and validation at startup. Production settings should be a file you can read rather than conditionals you evaluate in your head.
- “What must never be wrong in production?” -
DEBUG = False, since the debug page leaks tracebacks, locals and the settings module; a correctALLOWED_HOSTS; a realSECRET_KEYfrom the environment; and the secure cookie plus HSTS settings. - “Why validate settings at startup?” - a missing variable should crash on boot where the deploy pipeline sees it, not on the first request that reaches that code path. A
required()helper that raisesImproperlyConfiguredis three lines. - “How would you check a deployment is configured safely?” -
manage.py check --deployagainst the production settings module, in CI. It flags the whole security checklist without anyone having to remember it. - “What’s the subtle environment-variable bug?” - environment values are strings, so
"False"is truthy.DEBUG = os.environ.get("DEBUG", False)ships debug mode to production, and the code looks correct. - “What does
CONN_MAX_AGEdo?” - keeps database connections open across requests instead of reconnecting each time. Worth setting under load, and worth setting back to0behind a transaction-mode connection pooler, which does the pooling for you.