My Django settings file got exposed. What should I do?
Django's settings.py is the configuration core of your application. It contains the SECRET_KEY used for cryptographic signing across the entire framework, your database password, and any third-party API keys. A leaked SECRET_KEY is particularly serious — it must be rotated immediately, which will log out all users.
Rotate the SECRET_KEY before anything else — it's the skeleton key for your application.
Django uses SECRET_KEY to sign sessions, CSRF tokens, password reset links, and any data protected by django.core.signing. Anyone with the key can forge these. Changing it will log out all current users and invalidate all outstanding tokens — that's the correct response.
// the 60-second version
- Block access to the settings file and generate a new
SECRET_KEYimmediately. - Change the database password in the
DATABASESconfiguration. - Revoke all third-party API keys present in the settings file.
- Move all secrets to environment variables — keep them out of
settings.pypermanently.
01Understand what settings.py contains
A typical Django settings.py contains several categories of sensitive values:
SECRET_KEY— a 50+ character random string used by Django to cryptographically sign: session data (unless using database-backed sessions), CSRF tokens, password reset links and otherTimestampSignervalues, and any data signed withdjango.core.signing.DATABASES['default']['PASSWORD']— the password for your primary database. May also include separate passwords for replica or analytics databases.- Email backend settings —
EMAIL_HOST_PASSWORD, SendGrid or Mailgun API keys via third-party backends. - Custom API keys — Stripe, Twilio, AWS, and any other third-party services configured directly in settings rather than via environment variables.
ALLOWED_HOSTS— reveals your internal hostnames and IP addresses, which can aid further reconnaissance.
With the SECRET_KEY, an attacker can forge password reset links. This means they can request a password reset for any user, predict the token that will be emailed, and use it to take over the account — including admin accounts. Rotate immediately.
02Block access to the settings file
In a correctly deployed Django application, settings.py should never be web-accessible. It lives in the Django project package directory, which is typically above the web root. If it became accessible, something is wrong with the deployment structure or web server configuration.
Add a deny rule at the web server as defence in depth:
location ~* (settings|local_settings|production)\.py$ { deny all; return 404; }
Verify from outside your network that the file is no longer accessible. Also check whether any other Python files in the project are reachable — local_settings.py, settings_production.py, and similar files are common patterns.
03Rotate the SECRET_KEY and restart the application
Generate a new, cryptographically strong secret key. Django provides a utility for this:
# using Django's built-in key generator python -c "from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())" # or generate manually with Python's secrets module python -c "import secrets; print(secrets.token_urlsafe(50))"
Place the new key in your environment (not back into settings.py), update settings.py to read it from the environment, then restart the Django application. This will:
- Invalidate all existing session cookies — all users will be logged out.
- Invalidate all CSRF tokens — any open forms will need to be reloaded.
- Invalidate all outstanding password reset links — anyone with an old link will need to request a new one.
Inform your users of the session invalidation with an honest message about a security-related update.
04Change the database password
Change the password for the database user specified in DATABASES['default']:
-- connect as superuser ALTER USER django_user WITH PASSWORD 'new-strong-password'; -- update your environment variable -- export DATABASE_URL=postgres://django_user:new-password@localhost/mydb
Update settings.py to read the database password from an environment variable using a library like django-environ or the built-in os.environ.get():
import os SECRET_KEY = os.environ['DJANGO_SECRET_KEY'] DATABASES = { 'default': { 'ENGINE': 'django.db.backends.postgresql', 'NAME': os.environ['DB_NAME'], 'USER': os.environ['DB_USER'], 'PASSWORD': os.environ['DB_PASSWORD'], 'HOST': os.environ.get('DB_HOST', 'localhost'), } }
05Use environment variables for all secrets
The correct pattern is for settings.py to contain no secrets at all — only references to environment variables. Use a .env file for local development (excluded from git) and proper environment variable injection for production:
- Use django-environ or python-decouple to cleanly read from
.envin development and from real environment variables in production. - Add
settings.py,local_settings.py, and*.envto your.gitignore. - Keep a
settings.py.examplein the repository with placeholder values showing which environment variables are required. - For deployment, use your platform's native secret injection: Heroku Config Vars, AWS Systems Manager Parameter Store, GCP Secret Manager, or similar.
Run git log --all --full-history -- "*/settings.py" to check whether the settings file (with secrets) was ever committed to your repository. If it was, use BFG Repo Cleaner to purge it from history and rotate all credentials that appeared in any version of the file.
Was this guide useful?
These playbooks are free to read and share. If a heads-up ever saved you a bad week, you can say thanks — or jump into the other guides.