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
- The
.envfile isn't on the server. It's gitignored (correctly), so it never deployed, or.dockerignorekeeps it out of the image. - Nothing loads it.
python-dotenvordjango-environreads the file only when called. In production you should use real environment variables instead. - Your shell export doesn't reach the service. systemd, supervisor, Docker, cron and
sudostart with a clean environment. - The string
"False"is truthy.DEBUG = os.environ.get("DEBUG", False)makesDEBUGthe non-empty string"False", which isTruein a condition. - Different name or typo.
DJANGO_DEBUGversusDEBUG, or stray quotes and spaces in the value (KEY="abc"keeps the quotes in some tools). - 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
- Restart (or redeploy) after changing any variable; running processes keep their old environment.
- Keep secrets in a secrets store with access control, not in files copied around by hand.
- Fail on missing required values at startup (
os.environ[...]), and warn on suspicious ones.python manage.py check --deployflagsDEBUG = True(deployment checklist). - Specific errors this causes: SECRET_KEY must not be empty and DEBUG left on 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
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.