Most Django security and reliability problems in production come from settings left at their development defaults. Work through this Django production settings checklist before your first deploy and after big changes. Server setup (Gunicorn, Nginx, systemd) is covered in the linked deployment guides.
Before you start
- A Django project you can run locally.
- A way to provide environment variables on the server (systemd
EnvironmentFile, a.envfile read by django-environ, or Docker env files).
Core settings
import os
DEBUG = False
SECRET_KEY = os.environ["DJANGO_SECRET_KEY"]
ALLOWED_HOSTS = ["example.com", "www.example.com"]
CSRF_TRUSTED_ORIGINS = ["https://example.com", "https://www.example.com"]- DEBUG = False: debug pages leak settings and code. Never enable it on a public server.
- SECRET_KEY: unique per environment, from the environment, never in Git. Generate with
python -c "from django.core.management.utils import get_random_secret_key as k; print(k())". - ALLOWED_HOSTS: required when DEBUG is off; otherwise every request returns 400.
- CSRF_TRUSTED_ORIGINS: required for HTTPS form posts on Django 4+.
HTTPS behind Nginx
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
SECURE_HSTS_SECONDS = 31536000
SECURE_HSTS_INCLUDE_SUBDOMAINS = TrueNginx must send proxy_set_header X-Forwarded-Proto $scheme;, otherwise SECURE_SSL_REDIRECT causes an infinite redirect loop. Start HSTS with a short value (e.g. 3600) and raise it once HTTPS works everywhere.
Static and media files
STATIC_URL = "/static/"
STATIC_ROOT = BASE_DIR / "staticfiles"
MEDIA_URL = "/media/"
MEDIA_ROOT = BASE_DIR / "media"- Run
python manage.py collectstatic --noinputon each deploy. - Serve
STATIC_ROOTandMEDIA_ROOTfrom Nginx withalias, or static files with WhiteNoise. - Back up
MEDIA_ROOT: it holds user uploads, not in Git.
Database
- Use PostgreSQL or MySQL, not SQLite, for multi-user production sites.
- Read credentials from the environment (e.g.
DATABASE_URLwith dj-database-url). - Set
CONN_MAX_AGE = 60to reuse connections. - Run
python manage.py migrateon deploy, after backups.
Caching and sessions
Configure Redis as the cache backend (built into Django 4+) and optionally cache-backed sessions:
CACHES = {"default": {"BACKEND": "django.core.cache.backends.redis.RedisCache", "LOCATION": "redis://127.0.0.1:6379/1"}}Logging and error reports
LOGGING = {
"version": 1,
"handlers": {"console": {"class": "logging.StreamHandler"}},
"root": {"handlers": ["console"], "level": "WARNING"},
}Console logs end up in journalctl when running under systemd. Set ADMINS and email settings to receive error emails, or use an error-tracking service.
Set EMAIL_HOST, EMAIL_PORT, EMAIL_HOST_USER, EMAIL_HOST_PASSWORD, EMAIL_USE_TLS and DEFAULT_FROM_EMAIL so password resets work.
Run the deployment check
DJANGO_SETTINGS_MODULE=myproject.settings python manage.py check --deployDjango lists any remaining security warnings. Aim for none, or document why you accept each one.
Troubleshooting
- Bad Request (400) on every page:
ALLOWED_HOSTSdoes not include the domain. - Too many redirects:
SECURE_SSL_REDIRECTwithout the proxy header setting or header. - CSRF verification failed on login: missing
CSRF_TRUSTED_ORIGINSfor the https origin. - Admin without CSS:
collectstaticnot run, or Nginx not servingSTATIC_ROOT.
Django production FAQ
Should I use separate settings files?
Either separate files (base/dev/prod) or one file driven by environment variables works; never hard-code production secrets.
Is runserver acceptable behind Nginx?
No. Use Gunicorn or uWSGI; runserver is single-threaded and not hardened.
Can Django run on cPanel?
Yes, with Setup Python App and the same settings checklist.
