Django Settings Best Practice: Splitting base, local, and production Configs
A single settings.py creates real problems in production. The solution is a settings package — three files, clean separation, zero hacks. Here is exactly how to set it up.
mubashar
The default Django project gives you a single settings.py file. This works fine for a tutorial but creates real problems in production: you end up with hardcoded secrets, DEBUG = True accidentally shipped, or hacky if 'PRODUCTION' in os.environ blocks everywhere. The solution is a settings package — three files, clean separation, zero hacks.
The problem with one settings file
A single settings file tries to be everything to everyone. You need DEBUG = True locally but False in production. You need an SQLite database for development but PostgreSQL in production. You need console email output locally but real SMTP credentials in production. Handling all of this in one file means either hardcoding values, using fragile environment detection, or — worst of all — accidentally deploying development settings.
The settings package structure
myproject/
settings/
__init__.py # empty
base.py # shared by all environments
local.py # local development
production.py # production server
Delete your old settings.py — it conflicts with the new package.
base.py — the shared foundation
base.py contains everything that does not change between environments:
from pathlib import Path
BASE_DIR = Path(__file__).resolve().parent.parent.parent
INSTALLED_APPS = [
# Django core apps ...
# Your apps ...
]
MIDDLEWARE = [ ... ]
TEMPLATES = [ ... ]
ROOT_URLCONF = 'myproject.urls'
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}
STATIC_URL = '/static/'
STATIC_ROOT = BASE_DIR / 'staticfiles'
MEDIA_URL = '/media/'
MEDIA_ROOT = BASE_DIR / 'media'
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
Note that SECRET_KEY, DEBUG, and ALLOWED_HOSTS are intentionally not in base.py. They belong in the environment-specific files.
local.py — development settings
from .base import *
DEBUG = True
SECRET_KEY = 'django-insecure-dev-only-key-change-this'
ALLOWED_HOSTS = ['localhost', '127.0.0.1']
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
production.py — production settings
import os
from .base import *
DEBUG = False
SECRET_KEY = os.environ['SECRET_KEY']
ALLOWED_HOSTS = ['yourdomain.com', 'www.yourdomain.com']
SECURE_SSL_REDIRECT = True
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
SECURE_HSTS_SECONDS = 31536000
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = os.environ.get('EMAIL_HOST', '')
EMAIL_HOST_USER = os.environ.get('EMAIL_HOST_USER', '')
EMAIL_HOST_PASSWORD = os.environ.get('EMAIL_HOST_PASSWORD', '')
Pointing manage.py and wsgi.py at the right settings
In manage.py:
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings.local')
In wsgi.py and asgi.py:
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings.production')
On your production server, set DJANGO_SETTINGS_MODULE=myproject.settings.production and SECRET_KEY=... as real environment variables (in a .env file loaded by systemd, or via your hosting platform's secrets manager).
Running with a specific settings file
# Development (default via manage.py)
python manage.py runserver
# Explicitly override
python manage.py runserver --settings=myproject.settings.local
# Production check
python manage.py check --settings=myproject.settings.production --deploy
This structure has zero boilerplate and makes it impossible to accidentally ship DEBUG = True. Once you use it, you will never go back to a single settings file.
Written by
Mubashar Iqbal
Web developer, SEO expert, and independent maker. I build products, write about what I've learned, and create free tools for developers and marketers.