Django from Basics to Production · APIs with Django REST Framework, and Production

The Deployment Checklist

Run Django's deployment checks, fix the settings they flag, and prepare static files, secrets and a production server.

Create a free account to save this page, take notes and track your progress. Sign in

What you'll build

You'll run manage.py check --deploy against development-style settings and production settings, and see exactly which security warnings disappear: debug mode, secret key, HSTS, secure cookies, HTTPS redirects and allowed hosts.

The idea

  • python manage.py check --deploy inspects settings for common production mistakes.
  • Production needs: DEBUG = False, a long random SECRET_KEY from the environment, ALLOWED_HOSTS, HTTPS settings (SECURE_SSL_REDIRECT, SECURE_HSTS_SECONDS, secure cookies), and SECURE_PROXY_SSL_HEADER behind a TLS-terminating proxy.
  • Serve with a production server (Gunicorn or uvicorn for ASGI), collect static files (collectstatic, WhiteNoise or a CDN) and run migrations as a release step.

The code

manage.py check --deploy, before and after
import io
import re
import django
from django.conf import settings

base = dict(ROOT_URLCONF=__name__, INSTALLED_APPS=["django.contrib.contenttypes", "django.contrib.auth"],
            MIDDLEWARE=["django.middleware.security.SecurityMiddleware"],
            DATABASES={"default": {"ENGINE": "django.db.backends.sqlite3", "NAME": ":memory:"}})
urlpatterns = []

def deploy_check(**overrides):
    from django.core.management import call_command
    from django.core.management.base import SystemCheckError
    for key, value in overrides.items():
        setattr(settings, key, value)
    out = io.StringIO()
    try:
        call_command("check", deploy=True, stdout=out, stderr=out)
    except SystemCheckError as e:
        out.write(str(e))
    found = dict(re.findall(r"\(security\.(W\d+)\) ([^\n]+)", out.getvalue()))
    return "\n".join(f"  {code}: {msg[:70]}" for code, msg in sorted(found.items())) or "  none"

settings.configure(**base, DEBUG=True, SECRET_KEY="dev")
django.setup()
print("development settings:\n" + deploy_check())
print("production settings:\n" + deploy_check(
    MIDDLEWARE=["django.middleware.security.SecurityMiddleware", "django.middleware.csrf.CsrfViewMiddleware",
                "django.middleware.clickjacking.XFrameOptionsMiddleware"],
    DEBUG=False, SECRET_KEY="k" * 20 + "7Gq2#Lw9!vZ4^pR1&xN8$eT5@bY3*cM6",
    ALLOWED_HOSTS=["shop.example.com"], SECURE_SSL_REDIRECT=True, SECURE_HSTS_SECONDS=31536000,
    SECURE_HSTS_INCLUDE_SUBDOMAINS=True, SECURE_HSTS_PRELOAD=True, SESSION_COOKIE_SECURE=True,
    CSRF_COOKIE_SECURE=True))

Output

development settings:
  W002: You do not have 'django.middleware.clickjacking.XFrameOptionsMiddlewar
  W003: You don't appear to be using Django's built-in cross-site request forg
  W004: You have not set a value for the SECURE_HSTS_SECONDS setting. If your 
  W008: Your SECURE_SSL_REDIRECT setting is not set to True. Unless your site 
  W009: Your SECRET_KEY has less than 50 characters, less than 5 unique charac
  W018: You should not have DEBUG set to True in deployment.
  W020: ALLOWED_HOSTS must not be empty in deployment.
production settings:
  none

Run this on your computer — see “Get Started” for setup.

Common mistakes

  • Running manage.py runserver in production.
  • Committing SECRET_KEY to Git, or reusing the development key.
  • HTTPS redirects behind a proxy without SECURE_PROXY_SSL_HEADER, causing redirect loops.
  • Forgetting collectstatic, so CSS and JS 404 when DEBUG is off.

Key points

  • Run check --deploy in CI and fix its warnings.
  • Secrets and hosts from the environment; HTTPS and secure cookies on.
  • Production server, static files and migrations as part of the release.

Further reading: Deployment checklist · How to deploy with WSGI or ASGI

Practice questions

What does manage.py check --deploy do?

Answer

Runs Django's deployment system checks, warning about insecure settings such as DEBUG on, a weak SECRET_KEY, missing HSTS or insecure cookies.

Why set SECURE_PROXY_SSL_HEADER behind a load balancer?

Answer

TLS ends at the proxy, so Django sees HTTP; the header tells Django the original request was HTTPS, preventing redirect loops and insecure URL generation.

Where should SECRET_KEY come from in production?

Answer

From the environment or a secret manager, unique per deployment and never committed to version control.

Open in the app to mark lessons complete, take notes and track your progress.

Was this page helpful?