How should a Django project load SECRET_KEY in production, and how do you generate a strong new value for it?
answer
- not in the repository
- fail loudly when missing
- the insecure prefix from startproject
- a management utility helper
basics
~10 sRead SECRET_KEY from the environment or a secrets file with no hard-coded default, so a missing value fails loudly. Generate one with django.core.management.utils.get_random_secret_key(), which returns 50 random characters; never ship startproject's 'django-insecure-' key.
solid answer
~40 s`startproject` writes a random key into `settings.py`, but prefixes it with `django-insecure-` to mark it as unfit for production, because it is now in source control. In production load it from outside the code, for example `SECRET_KEY = os.environ["DJANGO_SECRET_KEY"]`, so a missing variable raises at startup instead of silently falling back. Avoid `os.environ.get("DJANGO_SECRET_KEY", "dev-key")`: the committed default becomes the production key the day the variable is missing. Generate values with `get_random_secret_key()` from `django.core.management.utils`, 50 characters from a mixed alphabet via `get_random_string()`. `check --deploy` warns (`security.W009`) when the key is shorter than 50 characters, has fewer than 5 unique characters, or still carries the `django-insecure-` prefix; an empty key raises `ImproperlyConfigured`.
code
python · 10 lines# settings/production.py
import os
# KeyError at startup if the variable is missing: no silent default.
SECRET_KEY = os.environ["DJANGO_SECRET_KEY"]
# Comma-separated previous keys during a rotation; empty otherwise.
SECRET_KEY_FALLBACKS = [
key for key in os.environ.get("DJANGO_SECRET_KEY_FALLBACKS", "").split(",") if key
]go deeper
Keep SECRET_KEY out of the repository, load it from the environment, and generate it with get_random_secret_key().
Explain the django-insecure- prefix, why os.environ[...] beats .get() with a default, and what W009 checks.
Enforce one key per environment and the same key per instance, review for silent defaults, and prepare the rotation path in advance.
Own how secrets reach Django across services and environments, so key hygiene is a platform guarantee rather than per-project discipline.
## Where the first key comes from `django-admin startproject` generates a **random `SECRET_KEY`** and writes it into `settings.py`. Since the key is then committed with the rest of the project, Django deliberately prefixes it with **`django-insecure-`**. The prefix is a label, not a weakness in the randomness: it tells you, and the deployment checks, that this value has been exposed in source control and must be replaced before production. ## Generating a proper key Django ships a helper for exactly this job: - **`get_random_secret_key()`** in `django.core.management.utils` returns a **50-character** string drawn from lowercase letters, digits and punctuation, using `get_random_string()`, which is built on the `secrets` module. - It is the same function `startproject` uses, minus the insecure prefix. A one-liner from a shell with Django installed: ```bash python -c "from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())" ``` Paste the output into your secret store, not into a file in the repository. ## Loading it without a silent default The common failure is not a weak key but a **committed fallback**. Compare three patterns: | Pattern | What happens when the variable is missing | |---|---| | `SECRET_KEY = os.environ["DJANGO_SECRET_KEY"]` | `KeyError` at import, the process does not start | | `SECRET_KEY = os.environ.get("DJANGO_SECRET_KEY")` | `None`; the first access to `settings.SECRET_KEY` raises `ImproperlyConfigured` | | `SECRET_KEY = os.environ.get("DJANGO_SECRET_KEY", "dev-key")` | the committed `dev-key` silently becomes the production key | The third row is the one to spot in code review. Anyone who can read the repository can then forge sessions, signed cookies and signed links for production. A separate development settings module, or a local `.env` file that is never committed, gives developers a key without putting one in code that production also runs. ## What Django checks for you 1. **Empty key.** `settings.SECRET_KEY` raises `ImproperlyConfigured("The SECRET_KEY setting must not be empty.")` when the value is empty, so Django refuses to run without one. 2. **Weak key.** `manage.py check --deploy` runs `check_secret_key`, which emits **`security.W009`** unless the key has at least **50 characters**, at least **5 unique characters**, and does not start with `django-insecure-`. 3. **Weak fallbacks.** The same criteria apply to each entry of `SECRET_KEY_FALLBACKS`, reported as **`security.W025`**. These checks catch mistakes; they cannot tell whether a strong-looking key has leaked. ## Keys in tests and local development Development and test runs need a key too, but not the production one: - A **test settings module** can set any fixed, non-empty value; tests never need a secret that matters, and `override_settings(SECRET_KEY=...)` lets a single test prove that a signature fails after a key change. - A developer's **local key** can live in an uncommitted `.env` file or in a development-only settings module that production never imports. - **Continuous integration** should use its own throwaway value rather than a copy of production's key, because build logs and caches are read by more people than production is. The principle is the same everywhere: a key's value should be known only to the environment whose signatures it protects. ## Good habits - One key **per environment**: staging must not be able to forge production's cookies. - The **same** key on every instance of one environment, or users are logged out as requests hop between servers. - Keep the key out of logs, error pages and container image layers; `DEBUG = False` matters here because the debug page is where settings tend to leak, although Django masks settings whose names contain `SECRET` or `KEY` in that page. - Plan how you will **rotate** it before you need to, using `SECRET_KEY_FALLBACKS`.
- Is a key starting with django-insecure- actually weak cryptographically?Not necessarily: the characters after the prefix come from the same `get_random_secret_key()` generator. The prefix marks that the value was written into `settings.py` by `startproject` and is therefore in source control. Its exposure, not its randomness, is the problem, which is why `check --deploy` flags it.
- Does the Django debug page leak SECRET_KEY when DEBUG is on?The technical 500 page lists settings, but Django's exception reporter replaces values whose names match sensitive patterns, including `SECRET` and `KEY`, with asterisks. That masking is a safety net, not a reason to run `DEBUG = True` in production, where the page exposes plenty of other information.
saying these in an interview costs you the question
- Keeping the key startproject generated because it is already random
- Using os.environ.get with a hard-coded default key for convenience
- Giving every environment the same key so sessions work everywhere
- Generating the key with uuid4 or a short word because length does not matter
- Believing Django runs happily with an empty SECRET_KEY in production