Why settings need environment configuration
Django settings control behavior that must differ between development and production: debug output, allowed hosts, database credentials, cookie security, HTTPS handling, and more. If you keep a single settings.py with hard-coded values, you risk leaking secrets, deploying with unsafe defaults, or making changes that accidentally break another environment.
A maintainable approach has two goals:
- Same codebase runs in multiple environments (local, staging, production).
- Secrets and environment-specific values are injected at runtime (environment variables or secret managers), not committed to Git.
Two common patterns for multi-environment settings
Pattern A: Separate settings modules (recommended for clarity)
You create a base settings module and then import/override per environment.
config/ # your Django project package (name may differ) settings/ __init__.py base.py dev.py prod.pyThen you select the module via DJANGO_SETTINGS_MODULE:
# Development (example)export DJANGO_SETTINGS_MODULE=config.settings.dev# Production (example)export DJANGO_SETTINGS_MODULE=config.settings.prodPattern B: Single settings module driven by environment variables
You keep one settings.py and branch based on DEBUG or ENV. This can work, but tends to grow into many conditional blocks and becomes harder to audit. If you choose this pattern, keep the branching minimal and group related settings.
Continue in our app.
You can listen to the audiobook with the screen off, receive a free certificate for this course, and also have access to 5,000 other free online courses.
Or continue reading below...Download the app
Step-by-step: Implement separate settings modules
1) Create a base settings file
Move shared configuration into config/settings/base.py. Keep it free of environment-specific secrets and values.
# config/settings/base.pyfrom pathlib import Pathimport osBASE_DIR = Path(__file__).resolve().parent.parent.parentdef env(name, default=None): return os.environ.get(name, default)INSTALLED_APPS = [ # ... your apps ...]MIDDLEWARE = [ # ... your middleware ...]ROOT_URLCONF = "config.urls"TEMPLATES = [ # ...]WSGI_APPLICATION = "config.wsgi.application"LANGUAGE_CODE = "en-us"TIME_ZONE = "UTC"USE_I18N = TrueUSE_TZ = TrueSTATIC_URL = "static/"DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"2) Add development settings
Development typically enables debug, uses a local database, and relaxes some security constraints.
# config/settings/dev.pyfrom .base import *DEBUG = TrueSECRET_KEY = env("DJANGO_SECRET_KEY", "dev-only-insecure-key")ALLOWED_HOSTS = ["localhost", "127.0.0.1"]DATABASES = { "default": { "ENGINE": "django.db.backends.sqlite3", "NAME": BASE_DIR / "db.sqlite3", }}# Helpful in development (optional)EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"Note: The development SECRET_KEY can be a fallback, but it should never be used in production. In production, require it to be set.
3) Add production settings
Production should disable debug, require a strong secret key, restrict allowed hosts, use a production database, and enable security settings.
# config/settings/prod.pyfrom .base import *def require_env(name): value = env(name) if not value: raise RuntimeError(f"Missing required environment variable: {name}") return valueDEBUG = FalseSECRET_KEY = require_env("DJANGO_SECRET_KEY")ALLOWED_HOSTS = require_env("DJANGO_ALLOWED_HOSTS").split(",")# Database example: PostgreSQL via DATABASE_URL or individual varsDATABASES = { "default": { "ENGINE": "django.db.backends.postgresql", "NAME": require_env("POSTGRES_DB"), "USER": require_env("POSTGRES_USER"), "PASSWORD": require_env("POSTGRES_PASSWORD"), "HOST": require_env("POSTGRES_HOST"), "PORT": env("POSTGRES_PORT", "5432"), "CONN_MAX_AGE": 60, }}# Security-related settings (typical baseline)SECURE_SSL_REDIRECT = TrueSESSION_COOKIE_SECURE = TrueCSRF_COOKIE_SECURE = TrueSECURE_HSTS_SECONDS = int(env("DJANGO_HSTS_SECONDS", "31536000"))SECURE_HSTS_INCLUDE_SUBDOMAINS = TrueSECURE_HSTS_PRELOAD = TrueSECURE_CONTENT_TYPE_NOSNIFF = TrueSECURE_REFERRER_POLICY = "same-origin"CSRF_TRUSTED_ORIGINS = require_env("DJANGO_CSRF_TRUSTED_ORIGINS").split(",")These values are examples; adjust based on your deployment (reverse proxy, load balancer, domain setup). For instance, if TLS is terminated at a proxy, you may also need:
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")USE_X_FORWARDED_HOST = True4) Point manage.py / wsgi.py / asgi.py to the default settings module
Typically, Django’s generated files reference config.settings. With a settings package, you can keep config/settings/__init__.py to select a default (often dev) for local work, but production should always set DJANGO_SETTINGS_MODULE explicitly.
# config/settings/__init__.py# Optional: default to dev for local convenience# In production, set DJANGO_SETTINGS_MODULE explicitlyfrom .dev import *If you prefer not to have a default (safer), leave __init__.py empty and always set DJANGO_SETTINGS_MODULE in your environment.
Managing configuration without committing secrets
Use environment variables for secrets and environment-specific values
At minimum, treat these as secrets or environment-specific:
SECRET_KEY- Database credentials
- Third-party API keys
- Email provider credentials
In local development, you can use a .env file loaded by your process manager or tooling. Django does not load .env automatically; you can load it via a library (commonly python-dotenv) or by exporting variables in your shell. If you use a .env file, add it to .gitignore and commit a template instead.
# .gitignore.env# .env.example (commit this, without real secrets)DJANGO_SETTINGS_MODULE=config.settings.devDJANGO_SECRET_KEY=change-meDJANGO_ALLOWED_HOSTS=example.com,www.example.comDJANGO_CSRF_TRUSTED_ORIGINS=https://example.com,https://www.example.comPOSTGRES_DB=appdbPOSTGRES_USER=appuserPOSTGRES_PASSWORD=change-mePOSTGRES_HOST=127.0.0.1POSTGRES_PORT=5432Fail fast when required production values are missing
Production should raise an error at startup if critical variables are missing. This prevents silent insecure defaults (for example, running with a development secret key).
Use a helper like require_env (shown earlier) for production-only settings.
Core settings to configure per environment
SECRET_KEY
SECRET_KEY signs cookies and tokens. If it leaks, attackers may forge sessions or tamper with signed data. Requirements:
- Unique per environment (dev/staging/prod).
- Never committed to source control.
- Rotated carefully (rotation invalidates existing sessions and signed data).
DEBUG
DEBUG=True shows detailed error pages and may expose sensitive information. In production, it must be False. Also ensure you have proper error logging/monitoring in production, because you won’t see debug pages.
ALLOWED_HOSTS
When DEBUG=False, Django requires ALLOWED_HOSTS to prevent Host header attacks. Configure it to your domain(s) and any internal hostnames used by your infrastructure.
# Example environment variableDJANGO_ALLOWED_HOSTS=example.com,www.example.comDatabases
Common approach:
- Development: SQLite for simplicity, or local Postgres if you want parity.
- Production: managed Postgres/MySQL with credentials in environment variables.
Also consider:
CONN_MAX_AGEto reuse DB connections in production.- Separate read replicas or transaction settings only when needed.
Security-related settings (production baseline)
| Setting | Typical production value | Purpose |
|---|---|---|
SECURE_SSL_REDIRECT | True | Redirect HTTP to HTTPS |
SESSION_COOKIE_SECURE | True | Session cookie only over HTTPS |
CSRF_COOKIE_SECURE | True | CSRF cookie only over HTTPS |
SECURE_HSTS_SECONDS | e.g. 31536000 | Enable HSTS (after verifying HTTPS works) |
SECURE_HSTS_INCLUDE_SUBDOMAINS | True | Apply HSTS to subdomains |
SECURE_HSTS_PRELOAD | True (optional) | Allow HSTS preload (requires care) |
SECURE_CONTENT_TYPE_NOSNIFF | True | Prevent MIME sniffing |
SECURE_REFERRER_POLICY | same-origin | Control referrer header behavior |
CSRF_TRUSTED_ORIGINS | Your HTTPS origins | Required when behind certain proxies and for cross-origin POSTs |
If you serve static files via a CDN or separate domain, also review CORS and cookie domain settings carefully (only if your architecture requires it).
How to verify environment-specific behavior
Check which settings module is active
From the command line, you can verify the selected settings module and key values.
# Show active settings modulepython -c "import os; print(os.environ.get('DJANGO_SETTINGS_MODULE'))"# Inspect settings via Django shellpython manage.py shell -c "from django.conf import settings; print(settings.DEBUG, settings.ALLOWED_HOSTS)"Confirm production safety checks
Django includes a deployment check framework. Run it with your production settings:
DJANGO_SETTINGS_MODULE=config.settings.prod python manage.py check --deployThis flags common issues such as missing HTTPS-related settings, insecure cookie flags, and debug configuration.
Smoke-test host and HTTPS assumptions
- With
DEBUG=False, request the site using a host not inALLOWED_HOSTSand confirm it is rejected. - If using a reverse proxy, confirm Django sees the request as HTTPS when appropriate (may require
SECURE_PROXY_SSL_HEADER).
Maintainability techniques for settings
Group settings by concern
In base.py, keep sections like: core, apps, middleware, templates, database defaults, internationalization, static/media, logging, security defaults. In dev.py and prod.py, only override what differs.
Use small helper functions for parsing env vars
Parsing booleans and lists repeatedly leads to mistakes. Keep helpers in base.py:
def env_bool(name, default="0"): return env(name, default).lower() in {"1", "true", "yes", "on"}def env_list(name, default=""): value = env(name, default) return [item.strip() for item in value.split(",") if item.strip()]Then:
DEBUG = env_bool("DJANGO_DEBUG", "0")ALLOWED_HOSTS = env_list("DJANGO_ALLOWED_HOSTS")Keep secrets out of logs and error pages
Avoid printing environment variables or settings in logs. If you add custom diagnostics, redact values like database passwords and secret keys.
Checklist: what changes between development and production
| Area | Development | Production |
|---|---|---|
DEBUG | True | False |
SECRET_KEY | May use local fallback | Required env var, strong and private |
ALLOWED_HOSTS | localhost, 127.0.0.1 | Real domains only |
| Database | SQLite or local DB | Managed DB with credentials in env vars |
| HTTPS | Often off | On; redirect HTTP to HTTPS |
| Cookies | *_SECURE=False acceptable | SESSION_COOKIE_SECURE=True, CSRF_COOKIE_SECURE=True |
| HSTS | Off | On after verifying HTTPS is correct |
| Error handling | Debug pages | Logging/monitoring; no debug pages |
| Config source | Local env or .env (ignored) | Environment variables / secret manager |
| Verification | Basic runserver checks | manage.py check --deploy + smoke tests |