Development Long read Deploy
How do you ship Django to production?

How do you ship Django to production?

Settings split, security headers, and environment variables.

10 February 2026 22 min read
Share
X in

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 = True

DEBUG 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.

  1. File/remote handler instead of console when DEBUG=False
  2. Exception tracking with Sentry SDK
  3. Do not log secrets (SQL, tokens, passwords)
  4. 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