deployment

Django Settings for Production

How to split Django settings for development and production, manage secrets, configure allowed hosts, database connections, static files, and security middleware.

⏱ 15 min read production
Server configuration dashboard with environment variable management

Django settings control nearly every aspect of how your application behaves, from database connections and caching backends to security headers and email delivery. Getting settings right for production is not about flipping a single DEBUG = False switch. It requires separating environment-specific values, managing secrets securely, hardening security middleware, and ensuring your configuration is reproducible across deploys. This guide covers the full production settings workflow I use during real deployments, including settings splitting, environment variables, database configuration, static file handling, and the security middleware chain. For broader deployment context, see our Deployment hub.

The default settings.py file Django generates is deliberately simple. It uses SQLite, enables debug mode, and trusts localhost. None of that survives first contact with real traffic. The goal here is to transform those defaults into a production-grade configuration that works reliably across development, staging, and live environments.

Splitting settings into modules

The most effective pattern is a settings package with a shared base and environment-specific overrides:

myproject/
  settings/
    __init__.py
    base.py
    dev.py
    production.py
    test.py

In __init__.py:

# Empty or import a default

In base.py, place everything shared: installed apps, middleware stack, template configuration, authentication backends, internationalization, and any setting that does not change between environments.

In dev.py:

from .base import *

DEBUG = True
ALLOWED_HOSTS = ['localhost', '127.0.0.1']
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': BASE_DIR / 'db.sqlite3',
    }
}
INSTALLED_APPS += ['debug_toolbar']
MIDDLEWARE.insert(0, 'debug_toolbar.middleware.DebugToolbarMiddleware')
INTERNAL_IPS = ['127.0.0.1']

In production.py:

import os
from .base import *

DEBUG = False
ALLOWED_HOSTS = os.environ.get('ALLOWED_HOSTS', '').split(',')
SECRET_KEY = os.environ['DJANGO_SECRET_KEY']

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': os.environ['DB_NAME'],
        'USER': os.environ['DB_USER'],
        'PASSWORD': os.environ['DB_PASSWORD'],
        'HOST': os.environ['DB_HOST'],
        'PORT': os.environ.get('DB_PORT', '5432'),
        'CONN_MAX_AGE': 600,
        'OPTIONS': {
            'connect_timeout': 10,
        },
    }
}

Set the active settings module through the DJANGO_SETTINGS_MODULE environment variable:

export DJANGO_SETTINGS_MODULE=myproject.settings.production

Environment variables and secret management

Hard-coded secrets in settings files are the single most common security mistake in Django projects. Every sensitive value must come from the environment.

Minimum secrets to externalize:

  • SECRET_KEY
  • Database credentials
  • Email SMTP passwords
  • API keys for third-party services
  • Sentry DSN or monitoring tokens

For local development, a .env file works well with django-environ or python-dotenv. In production, use your platform’s secret management: environment variables in Heroku, secrets in AWS Parameter Store, or Kubernetes secrets.

import environ

env = environ.Env()
environ.Env.read_env()  # Only reads .env if it exists

SECRET_KEY = env('DJANGO_SECRET_KEY')
DEBUG = env.bool('DEBUG', default=False)
DATABASES = {'default': env.db()}

Never commit .env files. Add them to .gitignore immediately.

ALLOWED_HOSTS and CSRF_TRUSTED_ORIGINS

Django requires ALLOWED_HOSTS when DEBUG = False. This protects against HTTP Host header attacks. Set it to your actual domain names:

ALLOWED_HOSTS = ['prodjango.com', 'www.prodjango.com']

For Django 4.0+, also configure CSRF_TRUSTED_ORIGINS:

CSRF_TRUSTED_ORIGINS = ['https://prodjango.com', 'https://www.prodjango.com']

Do not use wildcards in production. Wildcards defeat the purpose of the protection.

Security middleware configuration

Production Django should enable all security middleware and headers:

SECURE_SSL_REDIRECT = True
SECURE_HSTS_SECONDS = 31536000
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
SECURE_CONTENT_TYPE_NOSNIFF = True
SECURE_BROWSER_XSS_FILTER = True

The SECURE_PROXY_SSL_HEADER setting is critical when running behind a reverse proxy or load balancer that terminates SSL. Without it, Django sees all requests as HTTP and redirect loops can occur. The security checklist covers the full hardening workflow.

Static files and WhiteNoise

In production, Django does not serve static files itself. You need a solution. WhiteNoise is the simplest for single-server or PaaS deployments:

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'whitenoise.middleware.WhiteNoiseMiddleware',
    # ... rest of middleware
]

STATIC_URL = '/static/'
STATIC_ROOT = BASE_DIR / 'staticfiles'
STORAGES = {
    'staticfiles': {
        'BACKEND': 'whitenoise.storage.CompressedManifestStaticFilesStorage',
    }
}

Run collectstatic during deployment:

python manage.py collectstatic --noinput

For larger applications or CDN-based delivery, see our static files and media guide.

Caching configuration

Production applications benefit from a proper cache backend. Memcached or Redis are standard choices:

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.redis.RedisCache',
        'LOCATION': os.environ.get('REDIS_URL', 'redis://127.0.0.1:6379/1'),
    }
}

Cache the session backend too if you use database sessions and want to reduce load:

SESSION_ENGINE = 'django.contrib.sessions.backends.cached_db'

The caching patterns guide covers view caching, template fragment caching, and cache invalidation strategies.

Logging configuration

Production logging needs structure. Send warnings and errors to a central log service while keeping Django’s default console output for development:

LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'formatters': {
        'verbose': {
            'format': '{levelname} {asctime} {module} {message}',
            'style': '{',
        },
    },
    'handlers': {
        'console': {
            'class': 'logging.StreamHandler',
            'formatter': 'verbose',
        },
    },
    'root': {
        'handlers': ['console'],
        'level': 'WARNING',
    },
    'loggers': {
        'django': {
            'handlers': ['console'],
            'level': 'WARNING',
            'propagate': False,
        },
    },
}

For details on structured logging and monitoring integration, see the logging and monitoring guide.

Deployment checklist

Before every production deployment, verify these settings:

  • DEBUG = False
  • SECRET_KEY is unique, random, and not in source control
  • ALLOWED_HOSTS lists only your actual domains
  • Database credentials come from environment variables
  • SECURE_SSL_REDIRECT is True
  • HSTS headers are enabled
  • Static files are collected and served correctly
  • Logging captures errors and warnings
  • Email backend is configured for error notifications

Frequently asked questions

Can I keep everything in one settings.py? You can, using conditional logic based on environment variables. But settings modules are cleaner, easier to review, and less prone to accidental misconfiguration. The module approach also makes it obvious which settings apply where.

Should I use django-environ or python-dotenv? Both work. django-environ provides type casting and database URL parsing. python-dotenv is lighter and just loads variables. Pick one and standardize. The important thing is that secrets never appear in committed code.

What about django-configurations or django-split-settings? They are fine tools if your team prefers them. The package-based approach shown here uses no extra dependencies and is easy to understand for developers who are new to the project.

How do I handle settings for staging? Create a staging.py that imports from production.py and overrides only what differs, such as database host, logging level, or feature flags. Keep staging as close to production as possible.

When should I use connection pooling? Always in production. Set CONN_MAX_AGE to a reasonable value like 600 seconds. For high-traffic applications, use django-db-connection-pool or PgBouncer to manage connections more efficiently. The PostgreSQL production guide covers this in detail.