ImproperlyConfigured: Fix Django Settings Boot Errors
Export DJANGO_SETTINGS_MODULE, call django.setup() before models, then retry.
20+ years shipping production Python across data and backend systems. Written from production experience, not tutorials.
- ✓Basic Django: projects, apps, and manage.py
- ✓Running scripts with python3 and environment variables
- ✓How web servers load Python apps (WSGI basics)
- Export DJANGO_SETTINGS_MODULE in the process env and keep a setdefault in wsgi.py so every boot path resolves it
- Call django.setup() before importing any model — env var, setup, models, in that order, always
- Give standalone scripts the 3-line bootstrap header; prefer management commands for scheduled work
- Read SECRET_KEY strictly from env and list real hostnames in ALLOWED_HOSTS before the next deploy
Think of Django as a restaurant kitchen that won't start cooking until someone hands it the recipe book and turns on the gas. DJANGO_SETTINGS_MODULE is naming the recipe book. django.setup() is turning on the gas — the ovens (apps and models) can't work before that. SECRET_KEY and ALLOWED_HOSTS are the locks on the doors: forget them and the restaurant either won't open or won't let customers in.
You run a script, hit an endpoint, or deploy to a fresh server, and Django answers: ImproperlyConfigured: .... The message names a setting, an app, or a setup step, and the app won't boot until you satisfy it. Newcomers read this as Django being fussy; veterans read it as Django refusing to guess about configuration that must be explicit.
This error clusters around five situations: the settings module was never named, models got imported before , a standalone script skipped the bootstrap header, SECRET_KEY or ALLOWED_HOSTS diverged between environments, or the WSGI server boots through a different path than your dev server. Each one passes locally and fails in production, which is why this exception pages more often than it should.django.setup()
This guide walks all five with the exact fix for each, plus a 60-second triage that resolves most pages. You'll learn the bootstrap order Django requires, the standalone-script pattern that always works, and how to keep local and server environments from silently diverging.
DJANGO_SETTINGS_MODULE Unset: Name Every Boot Path
Every Django process begins with one question: which settings module describes this deployment? The answer comes from the DJANGO_SETTINGS_MODULE environment variable, a dotted path like mysite.settings. Without it, Django has no database configuration, no installed apps, no middleware — nothing it needs to serve a request or touch a model. Any settings access then raises ImproperlyConfigured naming the requested setting, which reads like a missing value but is really a missing identity.
The variable must be visible in the failing process, not just your shell. Exporting it in .bashrc helps your terminal but not systemd, Docker, or cron, each of which starts with its own environment. That's why Django's own templates belt-and-suspenders it: both manage.py and wsgi.py call os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'mysite.settings'), giving every boot path a default while letting the process env override per deployment.
Your check is two commands. First echo $DJANGO_SETTINGS_MODULE (plus the Python-level read) in the exact context that fails — the service shell, the container, the cron job. Then import the named module directly to prove it's on sys.path. If the variable is empty, export it where the process starts; if the import fails, fix the path or working directory. Most settings-module pages end right here.
Models Before django.setup(): Order Is Everything
Importing a Django model isn't a passive statement — it registers the model with the app registry, which only exists after settings load and apps populate. Call the import first and Django raises AppRegistryNotReady (or ImproperlyConfigured, depending on version and path), naming the step you skipped. The traceback points at your innocent-looking from myapp.models import Order on line 1, while the defect is the missing that should have run before it.django.setup()
The safe order never varies: set the environment variable, call , then import models. django.setup() loads settings, populates the app registry from INSTALLED_APPS, and wires up checks — the three preconditions every model import assumes. Management commands, the shell, and tests run this for you; standalone scripts, notebooks, and worker entry points must run it themselves.django.setup()
When triaging, grep the failing file for import order: model imports above the setup() call are the smoking gun. Move all Django imports below setup(), keep stdlib imports (os, sys) at top, and re-run the one-liner that imports a model after setup. If it prints, your order is fixed; if it still fails, the settings module or INSTALLED_APPS is the next suspect, not the import itself.
setup(), models — import order is the contract; grep top-of-file imports when this error appears.Standalone Scripts: The 3-Line Bootstrap Header
Standalone scripts are the most common source of this error because they boot Django by hand. A cron job, a data-fix script, or a queue worker entry point starts as a plain Python process with no settings loaded, and the first model import detonates. The fix is a three-line header at the top: os.environ.setdefault for the settings module, django.setup(), and only then the model imports. Everything Django-related lives below that header.
Two details break the header in practice. First, placement: the model imports must physically sit below setup() in the file — Python executes top to bottom, so an import on line 1 runs before setup() on line 5 no matter what you intended. Second, context: the script must run from the repo root (so mysite.settings is importable) with the project's virtualenv interpreter (so Django and deps exist). A script that's correct under your laptop's Python fails under the system Python with a ModuleNotFoundError wearing an ImproperlyConfigured costume.
For anything scheduled, skip the header entirely and write a management command. Commands run inside Django's configured context with logging, argument parsing, and transaction handling provided. The standalone header is for one-off fixes; recurring work deserves python manage.py nightly_sync, tested like any other Django code.
setup(), models — run from the repo root with the project interpreter, or use a management command.SECRET_KEY and ALLOWED_HOSTS Variants That Reject Traffic
Two settings produce their own ImproperlyConfigured flavors. An empty SECRET_KEY raises ImproperlyConfigured('The SECRET_KEY setting must not be empty'), and a hardcoded or weak key breaks sessions, password resets, and cryptographic signing — and Django's check framework loudly warns (security.W009) about the default placeholder in production, though it does not refuse to run. The operational failure is subtler than the check: local .env has a dev key, the server's process env lacks one, and the deploy either crashes at boot or, worse, ships with a committed test key every session can forge.
ALLOWED_HOSTS fails differently: with DEBUG=False and an empty list, Django returns 400 Bad Request for every request, which looks like a network or proxy outage rather than a setting. The page that should have listed hostnames instead shows a blank 400, and teams burn an hour on load-balancer logs before checking settings. The rule is simple: local dev may use empty hosts with DEBUG=True; every deployed environment lists its real hostnames explicitly.
Handle both at boot, not at request time. Read SECRET_KEY strictly so absence aborts startup with a clear message, generate real secrets with secrets.token_urlsafe(50) stored in a vault, and assert non-empty ALLOWED_HOSTS whenever DEBUG is off. A 10-line boot check in settings (or the deploy smoke test) converts both failure modes from midnight mysteries into deploy-time rejections.
WSGI vs Management Contexts: Two Doors, One Setting
The same codebase can pass under runserver and crash under gunicorn because the two servers boot Django through different doors. Management commands and runserver start from manage.py, which sets the settings default for you. Gunicorn, uWSGI, and systemd start from wsgi.py (or an ASGI module) plus the service's own environment — if neither names the settings module, workers die at import while your laptop stays green.
Systemd, Docker, and cron each add their own twist. Systemd units start with a minimal environment, so anything you exported in .bashrc is invisible; the unit needs an explicit Environment= line or an EnvironmentFile. Docker bakes ENV into the image, which is convenient until staging and production images diverge. Cron strips the environment almost bare, so Django cron jobs need the variable inline or in a wrapper script.
The durable fix declares the module in both layers: setdefault in code (wsgi.py, asgi.py, manage.py) and the explicit variable in process config (systemd unit, Dockerfile ENV, container task definition). Then add a smoke test that exercises the production boot path — importing the WSGI application and a model — rather than only testing through manage.py. When staging and production share the identical boot declaration, this error class disappears.
Reading the Traceback: The Quoted NAME Tells All
Every ImproperlyConfigured message quotes a NAME — the setting, app, or model it wanted. That quoted string is your fastest lead: Requested setting INSTALLED_APPS means no settings loaded (env var), No module named 'orders' means the app isn't installed or importable, Model class ... doesn't declare means app-label mismatch. Copy the quoted name verbatim instead of paraphrasing it in the ticket.
Run the triage in fixed order. First, the env var in the failing context (echo plus the Python-level read). Second, the setup order in the failing file (grep model imports vs setup call). Third, INSTALLED_APPS membership and importability of the named app. Fourth, SECRET_KEY/ALLOWED_HOSTS only if the message names them. This sequence resolves the large majority of reports because it follows Django's own boot sequence.
Write the triage down as a runbook, not tribal knowledge. A 5-line checklist in the on-call docs (with the exact commands from the debug guide above) turns a scary framework error into a routine check. The teams that stop fearing ImproperlyConfigured are the ones that made its diagnosis boring.
One Missing Env Var Looped 6 Workers for 52 Minutes
DJANGO_SETTINGS_MODULE. Under runserver and Docker-based staging the variable came from manage.py and the image ENV, so everything passed. On the systemd host, each of the 6 workers raised ImproperlyConfigured: Requested setting INSTALLED_APPS during import and exited; systemd restarted them in a loop. The load balancer TCP check passed because the socket briefly accepted, but every request got a 500. The variable had been dropped 3 weeks earlier during a playbook refactor that split web and worker roles, and 214 checkout orders failed in the 52-minute window.Environment=DJANGO_SETTINGS_MODULE=mysite.settings was added to myapp.service, the daemon reloaded, and all 6 workers booted cleanly. A /health/ endpoint that imports a model was added to the deploy smoke test, and the Ansible role now asserts the variable in both the image and the service file. The 214 queued orders processed in 6 minutes and the error rate returned to 0.02%.- Declare the settings module in every boot path (code default plus process env), not just the developer's favorite one.
- Smoke-test what the app does (import a model), not just that the port is open.
- Diff process environments between staging and production, not just code revisions.
echo $DJANGO_SETTINGS_MODULE; python -c "import os; print(os.environ.get('DJANGO_SETTINGS_MODULE'))". If either is empty, export it (export DJANGO_SETTINGS_MODULE=mysite.settings) and retry. Then confirm the module imports: python -c "import mysite.settings; print('settings ok', mysite.settings.__name__)".grep -n "from.*models import\|import django" script.py | head -20. The model import must come after django.setup(). Fix the order, then verify: python -c "import os, django; os.environ.setdefault('DJANGO_SETTINGS_MODULE','mysite.settings'); django.setup(); from myapp.models import Order; print('models ok')".python -c "import os, django; os.environ.setdefault('DJANGO_SETTINGS_MODULE','mysite.settings'); django.setup(); print('boot ok')". If that passes but script.py fails, the script's own imports or sys.path are wrong — run it from the repo root with the project interpreter: which python && python script.py.python -c "import os; print('KEY set:', bool(os.environ.get('SECRET_KEY'))); print('HOSTS:', os.environ.get('ALLOWED_HOSTS'))" and grep -rn "SECRET_KEY\|ALLOWED_HOSTS" mysite/settings.py | head. Missing KEY or empty HOSTS with DEBUG=False is the failure; inject via process env or secret manager, never by committing values.grep -n "DJANGO_SETTINGS_MODULE" mysite/wsgi.py manage.py; grep -rn "Environment=" /etc/systemd/system/myapp.service 2>/dev/null. Then simulate production boot: python -c "import os, django; os.environ.setdefault('DJANGO_SETTINGS_MODULE','mysite.settings'); django.setup(); import mysite.wsgi; print('wsgi boot ok')". Add the missing Environment= line and restart the service.| File | Command / Code | Purpose |
|---|---|---|
| django_settings_module.py | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mysite.settings") | DJANGO_SETTINGS_MODULE Unset |
| django_setup_order.py | ORDER = ["set DJANGO_SETTINGS_MODULE", "django.setup()", "import models"] | Models Before django.setup() |
| django_standalone_script.py | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mysite.settings") | Standalone Scripts |
| django_secret_hosts.py | key = os.environ.get("SECRET_KEY", "") | SECRET_KEY and ALLOWED_HOSTS Variants That Reject Traffic |
Key takeaways
django.setup(), then model imports — never reversed.Common mistakes to avoid
6 patternsExporting the settings var only in .bashrc
Top-of-file model imports above setup()
setup() call on line 5 never runs.django.setup(); keep only os/sys above it.Running scripts with the system Python outside the repo
Committing SECRET_KEY to the repo
Deploying with empty ALLOWED_HOSTS and DEBUG=False
Testing only through runserver before release
Interview Questions on This Topic
What is DJANGO_SETTINGS_MODULE and why is it required?
mysite.settings. Without it Django has no database, apps, or secret config, so any settings access raises ImproperlyConfigured. You set it as an environment variable, and wsgi.py plus manage.py both default it via setdefault. Every context — shell, scripts, servers, tests — must resolve it before touching Django.Frequently Asked Questions
20+ years shipping production Python across data and backend systems. Written from production experience, not tutorials.
That's Python Libraries. Mark it forged?
5 min read · try the examples if you haven't