Guides / Django environment variables not read in production (.env ignored)

Django environment variables not read in production (.env ignored)

The app uses the wrong settings in production: DEBUG stays on although you set DEBUG=False, the database host is empty, or os.environ["DJANGO_SECRET_KEY"] raises KeyError. Environment variables are per process, and they don't follow your code to the server. Here's how they get lost, and a small pattern that stops the surprises.

What it means

An environment variable exists only for the process that was started with it. Your .env file is just a text file; nothing reads it unless your code does. A service manager, container or cron job starts processes with its own environment, which does not include the exports from your shell.

Why it happens

  1. The .env file isn't on the server. It's gitignored (correctly), so it never deployed, or .dockerignore keeps it out of the image.
  2. Nothing loads it. python-dotenv or django-environ reads the file only when called. In production you should use real environment variables instead.
  3. Your shell export doesn't reach the service. systemd, supervisor, Docker, cron and sudo start with a clean environment.
  4. The string "False" is truthy. DEBUG = os.environ.get("DEBUG", False) makes DEBUG the non-empty string "False", which is True in a condition.
  5. Different name or typo. DJANGO_DEBUG versus DEBUG, or stray quotes and spaces in the value (KEY="abc" keeps the quotes in some tools).
  6. The process was not restarted after you changed the variable.

The fix

Parse every variable explicitly, with one small helper, and let missing required values fail at startup:

# settings.py
import os


def env_bool(name: str, default: bool = False) -> bool:
    value = os.environ.get(name)
    if value is None:
        return default
    return value.strip().lower() in {"1", "true", "yes", "on"}


def env_list(name: str, default: str = "") -> list[str]:
    return [item.strip() for item in os.environ.get(name, default).split(",") if item.strip()]


DEBUG = env_bool("DJANGO_DEBUG", default=False)   # safe default: off
SECRET_KEY = os.environ["DJANGO_SECRET_KEY"]       # required: KeyError if missing
ALLOWED_HOSTS = env_list("DJANGO_ALLOWED_HOSTS")

Now get the variables to the process, whichever way you run it:

# systemd: in the unit file
EnvironmentFile=/etc/myapp/env

# Docker
docker run --env-file ./production.env myimage

# a quick check of what the running app actually sees
python manage.py shell -c "import os; print(sorted(k for k in os.environ if k.startswith('DJANGO_')))"

For local development keep a .env file and load it only there, for example with python-dotenv guarded by if DEBUG, or via your tool (direnv, docker compose's env_file). Commit an .env.example with the names, never the values.

Making it stick in production

Or skip it

On Django Cloud you paste your settings in the dashboard, or run djangocloud env push. They are stored encrypted and never shown again, apply to the next release, and a rollback brings back the settings that release had.

pip install djangocloud-cli
djangocloud deploy

Get startedSee pricing

Free plans are available for students, AWS Activate startups, open-source, non-profit and research projects. See how Django Cloud compares with other ways to host Django.

Django is a registered trademark of the Django Software Foundation. Django Cloud is not affiliated with or endorsed by it. Found a mistake in this guide? Tell us and we will fix it.