skip to content

Per-Environment Configuration

DJANGO_SETTINGS_MODULE picks the settings module that django.conf.settings loads lazily, split per environment and fed from environment variables. Interviewers probe DEBUG and ALLOWED_HOSTS.

part ofDjangooverview, primer and where to startread it →
on this pageshow

explore

questions

4

In Django, what does DEBUG=True change, and what must you also set once DEBUG is False in production?

level: juniorimportance: must knowfreq 72%

answer

  1. error pages tell attackers a lot
  2. every query gets recorded
  3. localhost is trusted by default
  4. 400 Bad Request after the flip

basics

~20 s

DEBUG=True shows detailed tracebacks with settings and local variables, records every SQL query per connection, and trusts localhost when ALLOWED_HOSTS is empty. With DEBUG=False you must set ALLOWED_HOSTS, or every request returns 400 Bad Request.

solid answer

~40 s

`DEBUG` defaults to `False` in Django's global settings, but `startproject` writes `DEBUG = True`. In debug mode an unhandled exception renders a detailed page with the traceback, local variables and the current settings (names containing things like `KEY`, `SECRET`, `PASS` or `TOKEN` are masked, but paths and configuration still leak), a 404 lists the URL patterns tried, every SQL query is recorded in `connection.queries`, the template engine's `debug` option is on, and `runserver` serves static files. With `DEBUG = False` you get plain error pages and the default logging mails errors to `ADMINS`. You must then set `ALLOWED_HOSTS`: with debug on, an empty list allows `.localhost`, `127.0.0.1` and `[::1]`, but with it off an empty list means every request answers **Bad Request (400)**.

code

python · 6 lines
python
# payroll/settings/prod.py
from .base import *  # noqa: F403

DEBUG = False
ALLOWED_HOSTS = ["payroll.example.com"]
ADMINS = [("Payroll Ops", "[email protected]")]

go deeper

for a junior

Know that DEBUG shows detailed error pages, must be False in production, and that ALLOWED_HOSTS must be set once it is off.

for a middle

List the other effects of DEBUG, recorded queries, localhost trust, static serving, template debug, and explain the 400 errors after the switch.

for a senior

Read DEBUG from the environment with a safe default, parse it strictly, and make sure errors reach someone through ADMINS or logging when it is off.

for a principal

Make production-safe defaults the policy, so a missing variable can only make a deployment stricter, never turn debug output on.

## What DEBUG is `DEBUG` is a boolean setting. Its default in `django/conf/global_settings.py` is `False`, but the `settings.py` that `startproject` generates sets `DEBUG = True` for convenience — which is how it ends up on in production when nobody changes it. Django's settings reference is blunt: "Never deploy a site into production with `DEBUG` turned on." ## What DEBUG=True turns on | Area | With `DEBUG = True` | |---|---| | unhandled exceptions | a technical 500 page with traceback, local variables, request data and settings | | 404 responses | a technical 404 page listing the URL patterns Django tried | | SQL | every query is recorded in `connection.queries` | | host checking | an empty `ALLOWED_HOSTS` allows `.localhost`, `127.0.0.1` and `[::1]` | | static files | `runserver` from `django.contrib.staticfiles` serves them | | templates | the Django engine's `debug` option defaults to `DEBUG`, adding error context | | logging | the default logging config prints `django` messages to the console | The debug page masks settings whose names look sensitive — the docs list partial matches such as `API`, `KEY`, `PASS`, `SECRET`, `SIGNATURE` and `TOKEN` — but it still exposes file paths, installed apps, middleware and other configuration an attacker can use. On a payroll service, a local variable in a traceback might be an employee's salary record. ## The query log in detail With debug on, database wrappers log each query. The log is reset when a request starts and is capped at 9,000 entries per connection, so a web process does not grow without bound — but long-running management commands or worker processes outside the request cycle keep filling it until the cap, and the logging itself costs time. It is a development aid, not something to pay for in production. ## What changes when DEBUG is False 1. **Plain error pages** — Django renders `404.html` and `500.html` templates if present, or minimal defaults. 2. **Error reporting** — the default logging config sends `django` errors to the `mail_admins` handler, which only runs with debug off and mails the addresses in `ADMINS` (empty by default). 3. **ALLOWED_HOSTS is enforced strictly** — an empty list no longer falls back to localhost, so every request fails. The settings reference says failing to set it results in all requests "being returned as Bad Request (400)". 4. **Static files are no longer served by `runserver`** unless you pass `--insecure`; production needs a real static-file setup. ## Setting it per environment For a payroll service with development, staging and production, `DEBUG` should come from the environment with a **safe default**: ```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] ``` - Compare the string explicitly: `bool("False")` is `True`, so `bool(os.environ.get("DJANGO_DEBUG"))` turns debug on for any non-empty value. - `ALLOWED_HOSTS` must be a list or tuple; assigning the raw environment string raises `ImproperlyConfigured`. - Never change `DEBUG` at runtime; the docs say the only place to assign settings is a settings file. ## What an interviewer is listening for A junior candidate knows `DEBUG` shows tracebacks and must be off in production. A stronger one lists the other effects — query recording, localhost trust, static serving — and immediately adds that `ALLOWED_HOSTS` must be set when it is off, explaining the 400 errors that appear right after the switch.

  • Why do static files 404 right after switching DEBUG off in development?
    `django.contrib.staticfiles` makes `runserver` serve static files only when `DEBUG` is `True`, or when you run `runserver --insecure`. With debug off, Django expects static files to be collected and served by the production setup, so the development server stops serving them.
  • Does DEBUG=True leak SECRET_KEY on the error page?
    No. The debug page masks settings whose names contain sensitive fragments such as `KEY`, `SECRET`, `PASS` or `TOKEN`, so `SECRET_KEY` and `DATABASES` passwords are hidden. It still shows file paths, installed apps, middleware, local variables and request data, which is why debug mode must never be on in production.

saying these in an interview costs you the question

  • Thinks Django's own default for DEBUG is True
  • Believes the debug page is safe because it masks secrets
  • Expects an empty ALLOWED_HOSTS to work with DEBUG off
  • Parses DEBUG with bool() on the raw environment string
  • Toggles settings.DEBUG inside a view at runtime
open as a page

How do you give a Django project different settings for development, staging and production, and how does Django decide which settings module to load?

level: middleimportance: must knowfreq 60%

basics

~20 s

Django loads the settings module named by the DJANGO_SETTINGS_MODULE environment variable, or a command's --settings option. Per-environment setups use a shared base module with small dev, staging and prod modules, or one module reading environment variables, with secrets always from the environment.

open as a page

In Django, why is django.conf.settings a lazy object, and when would you call settings.configure() instead of setting DJANGO_SETTINGS_MODULE?

level: middleimportance: should knowfreq 40%

basics

~20 s

django.conf.settings is a LazySettings proxy: importing it loads nothing, and the first attribute access imports the module named by DJANGO_SETTINGS_MODULE. settings.configure() supplies settings in code instead, for standalone use of Django components; call it once, before any setting is read.

open as a page

In Django code, why is copying a setting into a module-level constant or default argument at import time a bug, and what should you do instead?

level: seniorimportance: should knowfreq 33%

basics

~20 s

A module-level TAX_RATE = settings.PAYROLL_TAX_RATE or a default argument freezes the value when the module is imported: test overrides cannot reach it, and importing the module before settings exist fails. Read settings inside functions at call time instead.

open as a page