skip to content

Why must DEBUG be False on a production Django site, and what stops working the moment you turn it off?

level: juniorimportance: must knowfreq 80%

answer

  1. what an error page reveals
  2. tracebacks, local variables, settings
  3. the host check stops being lenient
  4. static files and error templates

basics

~20 s

DEBUG=True shows tracebacks with local variables, request data and most settings to anyone who triggers an error, and keeps a SQL log in memory. Turning it off requires a real ALLOWED_HOSTS, another way to serve static files, and 404/500 templates.

solid answer

~40 s

With `DEBUG = True` any error renders Django's technical page: the traceback, local variables of each frame, request data and the settings, with only values whose names look sensitive (`KEY`, `SECRET`, `PASS`, `TOKEN` and similar) masked; a 404 lists your URL patterns. Django also logs every SQL query per connection, up to 9,000, and `check --deploy` flags it as `security.W018`. Note the trap: the global default is `False`, but `startproject` writes `DEBUG = True`. When you turn it off, three things change at once: an empty `ALLOWED_HOSTS` stops falling back to localhost names, so every request gets a 400; the staticfiles `runserver` handler stops serving static files; and errors render your `404.html`/`500.html` templates or Django's plain defaults, while the default logging config starts emailing errors to `ADMINS`.

code

python · 4 lines
python
import os

DEBUG = os.environ.get('DJANGO_DEBUG') == '1'
ALLOWED_HOSTS = [h for h in os.environ.get('DJANGO_ALLOWED_HOSTS', '').split(',') if h]

go deeper

for a junior

Know that DEBUG must be False in production because error pages leak code and data, and that ALLOWED_HOSTS must then list your domains.

for a middle

Explain everything DEBUG toggles: the technical error pages, the SQL log, the host fallback, static serving under runserver and the default logging behaviour.

for a senior

Make debug mode impossible to ship by accident: environment-driven settings defaulting to off, check --deploy in CI, and error reporting that replaces the debug page.

for a principal

Set an organisation-wide rule for production settings, where safe defaults live in code and every deviation must be explicit and reviewed.

## What DEBUG = True exposes `DEBUG` is the switch between Django's developer conveniences and its production behaviour. With it on, Django helps whoever is looking at the screen — which in production means anyone on the internet. - **Technical 500 page.** An unhandled exception renders the full traceback, the **local variables** of every frame, the request's GET, POST, cookies and META, and the project settings. Settings whose names match `API`, `AUTH`, `TOKEN`, `KEY`, `SECRET`, `PASS`, `SIGNATURE` or `HTTP_COOKIE` are masked; everything else — and every local variable — is shown as is. - **Technical 404 page.** A missing URL lists the URL patterns Django tried, which maps your whole application for an attacker. - **SQL query log.** Every query on every connection is recorded in `connection.queries`, up to 9,000 per connection, costing memory and time on every request. - **Relaxed host check.** With `ALLOWED_HOSTS` empty, Django accepts `localhost` variants so development just works. The deployment checks report `DEBUG = True` as **`security.W018`**: *You should not have DEBUG set to True in deployment.* ## The default trap Django's **global default is `DEBUG = False`**, but the `settings.py` that `startproject` generates sets **`DEBUG = True`**. A project that ships that file unchanged ships debug mode. The usual fix is to read `DEBUG` from the environment so that production can only turn it on deliberately. ## What changes when you turn it off | Area | With `DEBUG = True` | With `DEBUG = False` | |---|---|---| | unhandled exception | technical 500 page | your `500.html` template, or a plain default page | | unmatched URL | technical 404 listing URL patterns | your `404.html` template, or a plain default page | | empty `ALLOWED_HOSTS` | localhost names accepted | every request rejected with 400 Bad Request | | static files under `runserver` | served by the staticfiles handler | not served (unless `--insecure`) | | default `django` logger to console | INFO and above printed | discarded | | error emails to `ADMINS` | not sent | ERROR records emailed by the default config | | SQL query log | kept in memory | not kept (unless forced) | ## The launch checklist this implies 1. Set `DEBUG = False`, ideally from an environment variable that defaults to off. 2. Set `ALLOWED_HOSTS` to the domains you serve; leaving it empty turns every request into a 400. 3. Serve static files through something other than `runserver` after running `collectstatic`. 4. Add `404.html` and `500.html` templates; the 500 template is rendered without a request context, so it should not depend on context processors. 5. Decide where errors go now that the debug page is gone: `ADMINS` for email, and a `LOGGING` configuration that writes to the output your platform collects. 6. Run `manage.py check --deploy` against the production settings to catch the rest. ## Common misunderstandings - Turning `DEBUG` off does **not** make Django "faster" by itself in any dramatic way; the security exposure is the reason. - Masking only covers *settings* by name. A local variable called `card_number` in a view frame is displayed in full. - A site that works in staging with `DEBUG = True` and breaks with 400s in production is almost always an `ALLOWED_HOSTS` problem. ## What interviewers listen for The leak (tracebacks, locals, settings, URL map), the generated-template trap, and the concrete follow-on work: `ALLOWED_HOSTS`, static files, error templates and error reporting.

  • After setting DEBUG = False, every request returns 400 Bad Request. What is wrong?
    `ALLOWED_HOSTS` is empty or does not include the host clients use. With `DEBUG` on, an empty list falls back to localhost names; with it off, Django validates the `Host` header strictly and raises `DisallowedHost`, which becomes a 400. Add the production domain names to `ALLOWED_HOSTS`.
  • If Django masks sensitive settings on the debug page, why is DEBUG = True still dangerous?
    Masking works only on setting names that match patterns such as `KEY`, `SECRET` or `PASS`. Local variables in every traceback frame, request POST data, cookies and headers, non-matching settings and the full URL map are shown as is. One error page can reveal user data, internal hostnames and credentials held in ordinary variables.

saying these in an interview costs you the question

  • DEBUG defaults to True, so it only needs turning off once
  • The debug page masks all secrets, so leaving DEBUG on is only a cosmetic issue
  • An empty ALLOWED_HOSTS accepts any host when DEBUG is False
  • DEBUG = True stores an unlimited number of SQL queries per connection
  • Turning DEBUG off keeps runserver serving static files as before