Django settings SECRET_KEY 관리
Best practices for managing Django SECRET_KEY — environment variables, key rotation, security implications, and production deployment patterns.
Django settings SECRET_KEY 관리
Introduction
Django's SECRET_KEY is used for cryptographic signing — sessions, password reset tokens, CSRF tokens, and more. If this key is exposed, an attacker can forge any of these, including session cookies that grant admin access. Yet many Django projects still have the SECRET_KEY hardcoded in settings.py, sometimes even committed to version control.
When I set up a Django deployment in Lisbon, I was horrified to find that the original developer had committed the production SECRET_KEY to a public GitHub repository. The key had to be rotated immediately, and all existing sessions were invalidated.
Environment
Django 4.2.11
Python 3.12.3Problem
Problem 1: Hardcoded SECRET_KEY
# settings.py
SECRET_KEY = 'django-insecure-abc123def456ghi789' # BAD!This is the default generated by django-admin startproject. It says "insecure" right in the key — you must replace it.
Problem 2: SECRET_KEY in version control
# settings.py committed to git
SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY', 'fallback-key-here')
# If the env var is not set, you use a weak fallbackProblem 3: No key rotation strategy
# Old key that has been exposed
SECRET_KEY = 'old-key-that-was-once-public'
# No mechanism to rotate to a new keyProblem 4: Weak fallback key
import secrets
# This generates a new key every time settings.py is loaded
# Every restart invalidates all sessions
SECRET_KEY = secrets.token_hex(32)Analysis
The SECRET_KEY is used for:
- Session signing: Session cookies are signed with the key. If the key is known, an attacker can forge any session.
- CSRF tokens: These are signed with the key to prevent cross-site request forgery.
- Password reset tokens: The token sent in password reset emails is signed with the key.
- Cryptographic signing: Any data signed with
django.core.signing.Signerorsigning.dumps().
Why rotation is difficult:
When you change SECRET_KEY, all existing signed data becomes invalid:
- All user sessions are logged out
- All password reset tokens stop working
- All CSRF tokens become invalid
The security implications:
An exposed SECRET_KEY allows:
- Session forgery (login as any user)
- Admin access (forge admin session)
- CSRF bypass (submit forms on behalf of any user)
- Password reset token forgery
Solution
Fix 1: Use environment variables (minimum)
# settings.py
import os
SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY')
if not SECRET_KEY:
raise ValueError("DJANGO_SECRET_KEY environment variable is not set")# Set in your environment
export DJANGO_SECRET_KEY='your-secret-key-here'Fix 2: Use a .env file with python-dotenv
# settings.py
from pathlib import Path
from dotenv import load_dotenv
import os
BASE_DIR = Path(__file__).resolve().parent.parent
load_dotenv(BASE_DIR / '.env')
SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY')
if not SECRET_KEY:
raise ValueError("DJANGO_SECRET_KEY environment variable is not set")# .env (add to .gitignore!)
DJANGO_SECRET_KEY=your-secret-key-hereFix 3: Generate a strong SECRET_KEY
# Generate a secure key
import secrets
print(secrets.token_urlsafe(50))
# Or use Django's built-in function
from django.core.management.utils import get_random_secret_key
print(get_random_secret_key())Fix 4: Key rotation with multiple keys
# settings.py
import os
SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY')
PREVIOUS_SECRET_KEY = os.environ.get('DJANGO_PREVIOUS_SECRET_KEY')
# Django supports key rotation for signing
# The first key is used for signing, the second for verification
SECRET_KEY_FALLBACKS = []
if PREVIOUS_SECRET_KEY:
SECRET_KEY_FALLBACKS = [PREVIOUS_SECRET_KEY]Fix 5: Validate key strength at startup
# settings.py
import os
import re
SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY')
if not SECRET_KEY:
raise ValueError("DJANGO_SECRET_KEY environment variable is not set")
# Validate key strength
if len(SECRET_KEY) < 50:
raise ValueError("SECRET_KEY must be at least 50 characters long")
if SECRET_KEY.startswith('django-insecure-'):
raise ValueError("SECRET_KEY must not be the default insecure key")
# Check for common weak patterns
weak_patterns = [
r'^[a-z]+$', # Only lowercase letters
r'^[0-9]+$', # Only numbers
r'^password', # Starts with "password"
]
for pattern in weak_patterns:
if re.match(pattern, SECRET_KEY):
raise ValueError(f"SECRET_KEY matches weak pattern: {pattern}")Fix 6: Production deployment with Docker
# Dockerfile
FROM python:3.12-slim
# SECRET_KEY must be provided at runtime
# Do NOT set a default value
ENV DJANGO_SECRET_KEY=""
# Validate at startup
RUN python -c "import os; assert os.environ.get('DJANGO_SECRET_KEY'), 'SECRET_KEY not set'"
CMD ["gunicorn", "myproject.wsgi:application"]# docker-compose.yml
services:
web:
environment:
- DJANGO_SECRET_KEY=${DJANGO_SECRET_KEY}
secrets:
- django_secret_key
# ...Lessons Learned
- Never commit SECRET_KEY to version control — not even in a .env file that is tracked.
- Always validate SECRET_KEY at startup — do not let the application start without a valid key.
- Use environment variables or a secrets manager (Vault, AWS Secrets Manager) for production.
- Plan for key rotation — use
SECRET_KEY_FALLBACKSfor smooth transitions. - Add a pre-commit hook to scan for accidentally committed secret keys.
This blog does not accept any external sponsorships, affiliate marketing, or ad revenue.