Skip to content

DEBUG must be False in production

Severity: criticalApplies to: Django 4.0+Applies to: Django 5.2 LTSApplies to: Django 6.0Facts last verified 2026-07-26 against Django 6.0 (main)

DEBUG defaults to False in global_settings.py. Every deployment that leaks a debug page got there because a project setting, an environment variable or a container default turned it back on.

The reason this outranks everything else in the checklist is what the debug page contains. On an unhandled exception Django renders the traceback, the local variables in every frame, the settings module, and the SQL queries the request ran. Local variables are where credentials actually live — a database password read into a connection call, an API token in a request header, a decrypted session value.

the fix
settings.py
import os
DEBUG = os.environ.get("DJANGO_DEBUG", "") == "true"

Read the default from the environment as off, so that a missing variable fails closed. DEBUG = os.environ.get("DJANGO_DEBUG", True) fails open, and worse, DEBUG = os.environ.get("DJANGO_DEBUG") is a non-empty string like "False" — which is truthy.

verify it workedRun this in: framework cli
Terminal window
python manage.py check --deploy
if you see this
You should not have DEBUG set to True in deployment.

The deploy check found DEBUG truthy. This is the highest-value finding the check produces, and it is a warning rather than an error only because the check system cannot know you are deploying.

Read in: django/core/checks/security/base.py — id security.W018

Two things about DEBUG that are commonly believed and wrong

Section titled “Two things about DEBUG that are commonly believed and wrong”

Django does not sanitize everything on the debug page. It scrubs settings whose names look sensitive and function arguments marked with @sensitive_variables, but that is a name-matching heuristic. Anything it does not recognise is rendered.

DEBUG = True does not fully disable host validation. When DEBUG is True and ALLOWED_HOSTS is empty, Django validates the host against ['.localhost', '127.0.0.1', '[::1]'] rather than accepting anything — the fallback is in get_host(). This surprises people who expect local development to accept any Host header, and it is why a container reached by its service name can raise DisallowedHost in development. See ALLOWED_HOSTS.

before you ship this

Turning DEBUG off changes error handling, not just the error page. Django starts serving your 500.html template, and if that template does not exist or itself raises, you get a bare response instead. Render both 404.html and 500.html at least once before deploying.

DEBUG = False also stops Django serving static files through runserver. That is correct — in production a real server or WhiteNoise should serve them — but it means turning the setting off locally makes the site look broken in a way that has nothing to do with the setting.

  • ALLOWED_HOSTS — the other half of what DEBUG changes
  • SECRET_KEY — one of the things the debug page can disclose