Introduction
Development and production settings must not live in one file. DEBUG=True, loose ALLOWED_HOSTS, and a hardcoded SECRET_KEY ease local work but create serious vulnerabilities on a public server. This article walks through mandatory security configuration when moving Django to production.
Goal: a settings structure managed by environment variables, auditable and aligned with the OWASP Django cheat sheet.
Splitting Settings Files
base.py holds shared settings; development.py and production.py hold environment-specific overrides. Start with DJANGO_SETTINGS_MODULE=proj.settings.production.
- Use django-environ or python-decouple
- Never commit .env to git
- Separate SECRET_KEY per environment
# settings/production.py
from .base import *
DEBUG = False
ALLOWED_HOSTS = env.list('ALLOWED_HOSTS')
SECRET_KEY = env('SECRET_KEY')
SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = TrueDEBUG and SECRET_KEY
With DEBUG=False, ALLOWED_HOSTS is required or you get SuspiciousOperation. SECRET_KEY is critical for signing, sessions, and CSRF; never commit it. Plan rotation.
# Bad - never do this
SECRET_KEY = 'django-insecure-hardcoded-key'
DEBUG = True # in production
# Good
import os
SECRET_KEY = os.environ['DJANGO_SECRET_KEY']
DEBUG = os.environ.get('DJANGO_DEBUG', 'false').lower() == 'true'SECRET_KEY leakage compromises all sessions and signed tokens; rotate immediately.
HTTPS, HSTS, and Security Headers
SecurityMiddleware enables SECURE_SSL_REDIRECT, HSTS, and XSS protections. Behind a reverse proxy (nginx), set SECURE_PROXY_SSL_HEADER.
SECURE_HSTS_SECONDS = 31536000
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True
SECURE_CONTENT_TYPE_NOSNIFF = True
X_FRAME_OPTIONS = 'DENY'
SECURE_REFERRER_POLICY = 'strict-origin-when-cross-origin'
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')CSRF, Session, and Cookie Security
CSRF_COOKIE_SECURE and SESSION_COOKIE_SECURE send cookies only over HTTPS. CSRF_TRUSTED_ORIGINS is required when POSTing from a different subdomain frontend. SESSION_COOKIE_HTTPONLY and CSRF_COOKIE_HTTPONLY block JavaScript access.
- CSRF_TRUSTED_ORIGINS: ['https://app.example.com']
- SESSION_COOKIE_AGE for session lifetime
- SameSite=Lax or Strict policy
CSRF_TRUSTED_ORIGINS = ['https://blog.example.com']
SESSION_COOKIE_HTTPONLY = True
CSRF_COOKIE_HTTPONLY = True
SESSION_COOKIE_SAMESITE = 'Lax'Database and Static Files
Database passwords come from environment variables; SSL connections can be required. Serve static files via whitenoise or CDN; do not expose MEDIA_ROOT directly to the internet.
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': env('DB_NAME'),
'USER': env('DB_USER'),
'PASSWORD': env('DB_PASSWORD'),
'HOST': env('DB_HOST'),
'OPTIONS': {'sslmode': 'require'},
}
}Logging, Monitoring, and Error Reporting
LOGGING config streams production logs to files or central collectors (ELK, CloudWatch). ADMINS and AdminEmailHandler can email on critical errors; Sentry integration is preferred.
- File/remote handler instead of console when DEBUG=False
- Exception tracking with Sentry SDK
- Do not log secrets (SQL, tokens, passwords)
- Health check endpoint for load balancer
LOGGING = {
'version': 1,
'handlers': {
'console': {'class': 'logging.StreamHandler'},
},
'root': {'handlers': ['console'], 'level': 'WARNING'},
}Conclusion and Checklist
Production security is not one-time; apply a checklist before every deploy. Review django-deploy checklist and mozilla django-guideline docs regularly.
- DEBUG=False, strong SECRET_KEY, ALLOWED_HOSTS set
- HTTPS required, HSTS enabled
- Security updates: pip-audit / dependabot
- Backup and disaster recovery plan