How to Reset & Rotate Django SECRET_KEY: Complete Security Guide

Master the complete process of resetting and rotating Django’s SECRET_KEY safely. Learn the impacts, strategies for production environments, and best practices to protect your application.


Overview: Django SECRET_KEY and Why It Matters

The SECRET_KEY is Django’s cryptographic backbone. It’s used for:

  • Session management: Encrypting and signing session cookies
  • CSRF protection: Validating form submission tokens
  • Password reset tokens: Creating secure password reset links
  • Signed data: JSON signing and message framework operations
  • Cryptographic operations: Any cryptographic signing in your application

If your SECRET_KEY is compromised, an attacker can:

  • ✗ Forge session cookies and impersonate users
  • ✗ Reset user passwords or create new admin accounts
  • ✗ Tamper with signed data and forms
  • ✗ Decode sensitive information from cookies
  • ✗ Compromise the entire application

Why Reset Your SECRET_KEY?

Reason 1: Security Compromise (Emergency)

If your SECRET_KEY is exposed (GitHub commit, server logs, developer leak):

# ❌ COMPROMISED - reset immediately
SECRET_KEY = 'django-insecure-abc123xyz-exposed-on-github'

# ✅ RESET to new secure key
SECRET_KEY = 'django-insecure-new-random-secure-string'

Reason 2: Routine Security Rotation (Best Practice)

Enterprise security standards recommend rotating secrets periodically (quarterly, annually):

# Quarterly rotation for compliance and security
# Old key (generated 3 months ago)
SECRET_KEY = 'xyz789abc-generated-3-months-ago'

# New key (rotate every 3 months)
SECRET_KEY = 'new-fresh-secure-string-today'

Reason 3: Development Environment Changes

Resetting keys when onboarding new developers or after environment changes:

# Old key in shared repo (bad practice, but common)
SECRET_KEY = 'shared-insecure-key'

# Reset to new unique key for each environment
# Development: local unique key
# Staging: staging unique key
# Production: production unique key

What Breaks When You Reset SECRET_KEY?

Critical: Understand the immediate impacts before resetting in production.

1. All Active Sessions Invalidate

# When you reset SECRET_KEY:
# Django can no longer decode existing session cookies
# Users see: "Logged Out" - must login again

# Example impact:
# - 10,000 active users get logged out
# - Support gets 1,000 "Why did I get logged out?" emails
# - Dashboard shows sudden session reset spike

2. CSRF Tokens Become Invalid

<!-- Before reset -->
<form method="POST">
    {% csrf_token %}  <!-- Token signed with old key -->
    <input type="submit">
</form>

<!-- After reset -->
<!-- All old CSRF tokens are invalid -->
<!-- Users get "CSRF token missing or incorrect" errors -->

3. Password Reset Tokens Expire

# Any password reset links sent with the old key
# will become invalid after reset

# Example:
# User starts: "Forgot Password"
# Email sent: "Reset link valid for 1 hour"
# During that hour: SECRET_KEY rotates
# User clicks link: "Invalid token" error
# They have to restart the password reset process

4. Signed Data Becomes Unverifiable

# Any JSON Web Signatures (JWS) or signed data
# created with the old key can no longer be verified

from django.core import signing

# Signed data created with old key
signed_data = signing.dumps({'user_id': 123})
# After reset, attempting to load it fails:
# signing.loads(signed_data)  # BadSignature exception!

Summary: Impact Severity Matrix

Affected Component User Impact Severity Can Mitigate?
Active Sessions All users logged out 🔴 High YES (fallback keys)
CSRF Tokens Form submissions rejected 🔴 High YES (fallback keys)
Password Reset Links Can’t reset passwords 🟠 Medium Partial (manual reset)
Signed Data API errors, verification fails 🟠 Medium YES (fallback keys)

Key Insight: Duration Matters

The longer you keep old SECRET_KEY values in fallback keys, the more protected your application is:

# Very safe but not recommended (forever)
SECRET_KEY = 'new-key-today'
SECRET_KEY_FALLBACKS = [
    'old-key-1-year-ago',
    'old-key-2-years-ago',
    'old-key-3-years-ago',
]

# Recommended (production standard)
SECRET_KEY = 'new-key-today'
SECRET_KEY_FALLBACKS = [
    'old-key-7-days-ago',
    'old-key-14-days-ago',
    'old-key-30-days-ago',
]

# Risky (not recommended for production)
SECRET_KEY = 'new-key-today'
SECRET_KEY_FALLBACKS = []  # No fallback - all old sessions invalid!

Basic Reset: Development Environment

Step 1: Generate a New Secret Key

# Option 1: Use Django's built-in generator
from django.core.management.utils import get_random_secret_key
new_key = get_random_secret_key()
print(new_key)
# Output: 'l6x0)1-z6f8x^@)&8h5$+8v!n@#1k+j3$#7q8'
# Option 2: Use Python's secrets module (recommended)
import secrets
new_key = secrets.token_urlsafe(50)
print(new_key)
# Output: 'KrMfcjtXuA9RL5voYPGjpY79h_mNqK8_vL2Rd5xYz'
# Option 3: Use shell commands
# Linux/macOS
head -c 50 /dev/urandom | base64

# Windows PowerShell
[Convert]::ToBase64String([Random]::new().NextBytes(50))

Step 2: Update settings.py

# settings.py

# ❌ OLD WAY (hardcoded)
SECRET_KEY = 'django-insecure-old-compromised-key'

# ✅ NEW WAY (environment variable)
import os
SECRET_KEY = os.getenv('SECRET_KEY', 'fallback-dev-key')

# For development with new key
SECRET_KEY = 'django-insecure-your-new-generated-key'

Step 3: Update .env File (If Using)

# .env
SECRET_KEY=your-new-generated-key-here

Step 4: Test the Reset

# Verify settings loaded correctly
python manage.py shell
>>> from django.conf import settings
>>> print(settings.SECRET_KEY)
'django-insecure-your-new-key'

# Clear any old session data (development only!)
python manage.py clearsessions

# Restart development server
python manage.py runserver

Using Environment Variables (Best Practice)

Why Environment Variables?

  • ✅ Never commit secrets to version control
  • ✅ Different keys for each environment (dev, staging, prod)
  • ✅ Easy to rotate without code changes
  • ✅ Works with Docker, Kubernetes, CI/CD
  • ✅ Industry standard practice
See also  How to Set Timezone in Django (settings.py TIME_ZONE and USE_TZ Configuration)

Method 1: Using python-decouple (Recommended)

# Install
pip install python-decouple
# settings.py
from decouple import config

SECRET_KEY = config('SECRET_KEY')
DEBUG = config('DEBUG', default=False, cast=bool)
# .env (not committed to git)
SECRET_KEY=your-super-secret-key-here
DEBUG=True

Method 2: Using python-dotenv

# Install
pip install python-dotenv
# settings.py
import os
from dotenv import load_dotenv

load_dotenv()

SECRET_KEY = os.getenv('SECRET_KEY')
if not SECRET_KEY:
    raise ValueError("SECRET_KEY environment variable not set")

Method 3: System Environment Variables (Production)

# Set system environment variable
export SECRET_KEY="your-production-secret-key"

# Or in .bashrc/.bash_profile
echo "export SECRET_KEY='your-production-key'" >> ~/.bashrc
source ~/.bashrc
# settings.py
import os

SECRET_KEY = os.environ.get('SECRET_KEY')
if not SECRET_KEY:
    raise ImproperlyConfigured("SECRET_KEY environment variable must be set")

Method 4: Using Django-environ (Comprehensive)

# Install
pip install django-environ
# settings.py
import environ

env = environ.Env(
    DEBUG=(bool, False)
)

# Read from .env file
environ.Env.read_env()

SECRET_KEY = env('SECRET_KEY')
DEBUG = env('DEBUG')

.gitignore: Never Commit Secrets

# .gitignore
.env
.env.local
.env.*.local
*.key
*.pem

SECRET_KEY_FALLBACKS (Production Safe Method)

Django 3.2+ introduced SECRET_KEY_FALLBACKS to safely rotate keys without invalidating all sessions.

How It Works

# settings.py
SECRET_KEY = 'new-key-generated-today'
SECRET_KEY_FALLBACKS = [
    'old-key-from-7-days-ago',
    'old-key-from-14-days-ago',
    'old-key-from-30-days-ago',
]

# Django verification order:
# 1. Try to verify with SECRET_KEY (new)
# 2. If that fails, try each fallback key
# 3. If all fail, reject the signature/session

Safe Rotation Process (Recommended)

Phase 1: Add New Key as Fallback (Day 0)

# settings.py
SECRET_KEY = 'old-production-key-that-worked'
SECRET_KEY_FALLBACKS = [
    'new-key-generated-today',  # New key added as fallback first
    'key-from-7-days-ago',
    'key-from-14-days-ago',
]

# Deploy this change to all instances
# All new sessions now created with old key
# But new key is already trusted for verification

Phase 2: Promote New Key to PRIMARY (Day 1)

# settings.py
SECRET_KEY = 'new-key-generated-today'  # NOW primary
SECRET_KEY_FALLBACKS = [
    'old-production-key-that-worked',
    'key-from-7-days-ago',
    'key-from-14-days-ago',
]

# Deploy to all instances
# All new sessions now created with new key
# Old key still trusted for backward compatibility
# Users with old sessions can still access

Phase 3: Keep Fallbacks for Grace Period (7-30 days)

# settings.py (keep for 7-30 days)
SECRET_KEY = 'new-key-generated-today'
SECRET_KEY_FALLBACKS = [
    'old-production-key-that-worked',  # Still trusting old sessions
    'key-from-7-days-ago',
    'key-from-14-days-ago',
]

# During this period:
# - Old sessions still work
# - CSRF tokens still valid
# - Password reset links still functional
# - Zero user disruption

Phase 4: Remove Old Keys (Day 30+)

# settings.py (after grace period)
SECRET_KEY = 'new-key-generated-today'
SECRET_KEY_FALLBACKS = [
    'key-from-7-days-ago',
    'key-from-14-days-ago',
]

# Old key is no longer trusted
# Any remaining old sessions become invalid
# This is acceptable after 30-day grace period

Real-World Example: Rotating Every Month

# Month 1: Initial setup
SECRET_KEY = 'key-month-1'
SECRET_KEY_FALLBACKS = []

# Month 2: Rotate key
SECRET_KEY = 'key-month-2'
SECRET_KEY_FALLBACKS = ['key-month-1']

# Month 3: Rotate key again
SECRET_KEY = 'key-month-3'
SECRET_KEY_FALLBACKS = ['key-month-2', 'key-month-1']  # Keep last 2

# Month 4: Continue pattern
SECRET_KEY = 'key-month-4'
SECRET_KEY_FALLBACKS = ['key-month-3', 'key-month-2']  # Drop oldest

# Benefit:
# - Always have 30+ days of backward compatibility
# - Users never forcibly logged out
# - Sessions seamlessly continue working

Rotation Strategy for Production

Safe Rotation Plan for Distributed Environments

For applications running on multiple servers/containers:

Day 0: Generate new key
---------
SECRET_KEY_new = 'xyz-newly-generated'

Day 1: Deploy Step 1 (add new key as fallback)
---------
# Deploy to ALL instances simultaneously
SECRET_KEY = 'old-key-still-active'
SECRET_KEY_FALLBACKS = ['xyz-newly-generated']

# Verify all servers are updated:
# curl https://your-app.com/health  # Check all replicas

Day 2: Wait for sessions to stabilize
---------
# Monitor:
# - No CSRF token errors
# - No session validation failures
# - No support tickets about auth issues

Day 3: Deploy Step 2 (promote new key to primary)
---------
# Deploy to ALL instances simultaneously
SECRET_KEY = 'xyz-newly-generated'  # PRIMARY
SECRET_KEY_FALLBACKS = ['old-key-still-active']

Day 4-30: Grace period (keep fallback)
---------
# Continue with current config
# All is good, users don't notice change

Day 31+: Remove old key from fallbacks
---------
SECRET_KEY = 'xyz-newly-generated'
SECRET_KEY_FALLBACKS = []

Checklist for Production Rotation

Production SECRET_KEY Rotation Checklist
✓ Generate new key (use secrets.token_urlsafe)
✓ Store in secure vault (AWS Secrets Manager, HashiCorp Vault, etc.)
✓ Update settings.py with fallback strategy
✓ Test in staging environment (run full test suite)
✓ Brief support team (may see login-related issues)
✓ Deploy Step 1 to canary (1-2 servers)
✓ Monitor canary for 1-2 hours
✓ Deploy Step 1 to all instances
✓ Wait 24 hours, monitor for issues
✓ Deploy Step 2 (promote new key)
✓ Monitor all services for 7 days
✓ Document rotation in runbook
✓ Schedule next rotation (quarterly)
✓ Set calendar reminder for fallback cleanup
            

Real-World Implementation

Complete Django Settings with Key Rotation

# settings.py
import os
from pathlib import Path
from decouple import config

BASE_DIR = Path(__file__).resolve().parent.parent

# SECRET KEY - from environment variable
SECRET_KEY = config('SECRET_KEY', default='django-insecure-development-key')

# FALLBACK KEYS - for safe rotation
SECRET_KEY_FALLBACKS = config(
    'SECRET_KEY_FALLBACKS',
    default='[]',
    cast=lambda x: eval(x) if x else []
)

# For development
if not os.environ.get('SECRET_KEY'):
    print("⚠️  WARNING: SECRET_KEY not set. Using development default.")
    print("⚠️  Set SECRET_KEY environment variable for production!")

# For production - require SECRET_KEY to be set
if os.getenv('DJANGO_SETTINGS_MODULE') == 'config.settings.production':
    if SECRET_KEY.startswith('django-insecure'):
        raise ValueError(
            "Production SECRET_KEY must be a secure key. "
            "Set SECRET_KEY environment variable."
        )

.env Configuration Files

# .env.development
SECRET_KEY=django-insecure-dev-key-not-used-in-production
DEBUG=True

# .env.staging
SECRET_KEY=${STAGING_SECRET_KEY}  # Set via CI/CD
SECRET_KEY_FALLBACKS=['old-staging-key']
DEBUG=False

# .env.production (managed by CI/CD, not committed)
SECRET_KEY=${PRODUCTION_SECRET_KEY}  # Retrieved from vault
SECRET_KEY_FALLBACKS=['prod-key-7-days-old', 'prod-key-14-days-old']
DEBUG=False

Docker/Kubernetes Configuration

# docker-compose.yml
services:
  web:
    build: .
    environment:
      SECRET_KEY: ${SECRET_KEY}  # Injected at runtime
      SECRET_KEY_FALLBACKS: ${SECRET_KEY_FALLBACKS}
      DATABASE_URL: ${DATABASE_URL}
    volumes:
      - .env:/app/.env.local  # Only in development
# kubernetes secret example
apiVersion: v1
kind: Secret
metadata:
  name: django-secrets
type: Opaque
stringData:
  SECRET_KEY: "your-secure-random-key-from-vault"
  SECRET_KEY_FALLBACKS: '["old-key-1", "old-key-2"]'
  
---
apiVersion: v1
kind: Pod
metadata:
  name: django-app
spec:
  containers:
  - name: web
    image: django-app:latest
    envFrom:
    - secretRef:
        name: django-secrets

If Your SECRET_KEY Is Compromised

Emergency Response Plan

STEP 1: Immediate (Within 5 Minutes)

1. Generate new SECRET_KEY immediately
2. Update in production environment ONLY (no fallback yet)
3. Deploy and restart all app servers
4. Monitor for errors (sessions will invalidate - expected)
# Emergency reset (no fallback = all sessions invalid)
SECRET_KEY = 'new-emergency-key-generated-now'
SECRET_KEY_FALLBACKS = []  # No fallback in emergency

# Alternative: Keep brief fallback if you prefer graceful degradation
SECRET_KEY = 'new-emergency-key-generated-now'
SECRET_KEY_FALLBACKS = ['previous-safe-key']  # Only if previous not compromised

STEP 2: Short Term (Within 1 Hour)

1. Notify all users via email/notification
   - "Security update: You may need to re-login"
2. Force logout all active sessions (database cleanup)
3. Invalidate all password reset tokens
4. Review server logs for suspicious activity
5. Check for unauthorized admin accounts created
# Force logout all users
from django.contrib.sessions.models import Session
Session.objects.all().delete()

# Or per-user
from django.contrib.sessions.models import Session
from django.utils import timezone

for session in Session.objects.all():
    session_data = session.get_decoded()
    if session_data.get('_auth_user_id'):  # Has user logged in
        session.delete()

STEP 3: Medium Term (Within 24 Hours)

1. Audit all admin user accounts
2. Review all database changes made since compromise
3. Check API audit logs for unauthorized requests
4. Verify database backups were not accessed
5. Notify security team and incident response
6. Document timeline of compromise

STEP 4: Long Term (Within 1 Week)

1. Implement SECRET_KEY rotation automation
2. Add monitoring for SECRET_KEY access
3. Review security practices that allowed exposure
4. Implement secrets scanning in CI/CD
5. Update security policies
6. Post-incident review with team

What Attackers Can Do with Your SECRET_KEY

Attack Vector Timeline Mitigation
Forge admin session cookie Immediately Reset SECRET_KEY, audit admin accounts
Create fake password reset tokens Immediately Reset SECRET_KEY, force password reset
Decode signed API tokens Immediately Invalidate all tokens
Modify session data (CSRF bypass) Days (until sessions expire) Force logout, reset SECRET_KEY
See also  Understanding Django Apps: How Many Apps Should Your Project Have?

Automating Key Rotation

Django Management Command for Rotation

# your_app/management/commands/rotate_secret_key.py
import os
import secrets
from django.core.management.base import BaseCommand
from django.conf import settings

class Command(BaseCommand):
    help = 'Rotate Django SECRET_KEY safely'

    def handle(self, *args, **options):
        # Generate new key
        new_key = secrets.token_urlsafe(50)
        
        # Read current .env
        env_file = '.env'
        with open(env_file, 'r') as f:
            lines = f.readlines()
        
        # Update or add SECRET_KEY
        new_lines = []
        secret_key_found = False
        
        for line in lines:
            if line.startswith('SECRET_KEY='):
                old_key = line.split('=')[1].strip()
                new_lines.append(f'SECRET_KEY={new_key}\n')
                
                # Add old key to fallbacks
                fallback_line = f'SECRET_KEY_FALLBACKS=["{old_key}"]\n'
                if not any(l.startswith('SECRET_KEY_FALLBACKS=') for l in lines):
                    new_lines.append(fallback_line)
                
                secret_key_found = True
            else:
                new_lines.append(line)
        
        # Write back to .env
        with open(env_file, 'w') as f:
            f.writelines(new_lines)
        
        self.stdout.write(
            self.style.SUCCESS(f'✓ Secret key rotated. New key: {new_key[:20]}...')
        )

Usage:

python manage.py rotate_secret_key
python manage.py rotate_secret_key --deploy  # Also triggers deployment

CI/CD Pipeline Integration (GitHub Actions)

# .github/workflows/rotate-secret.yml
name: Rotate SECRET_KEY Monthly

on:
  schedule:
    - cron: '0 0 1 * *'  # First day of every month at midnight

jobs:
  rotate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      
      - name: Set up Python
        uses: actions/setup-python@v4
        with:
          python-version: '3.11'
      
      - name: Generate new SECRET_KEY
        run: |
          NEW_KEY=$(python -c "import secrets; print(secrets.token_urlsafe(50))")
          echo "NEW_SECRET_KEY=$NEW_KEY" >> $GITHUB_ENV
      
      - name: Update AWS Secrets Manager
        env:
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
        run: |
          aws secretsmanager update-secret \
            --secret-id django/production/secret-key \
            --secret-string "$NEW_SECRET_KEY"
      
      - name: Deploy to production
        env:
          DEPLOYMENT_TOKEN: ${{ secrets.DEPLOYMENT_TOKEN }}
        run: |
          # Trigger deployment with new key
          curl -X POST https://deploy.company.com/api/deploy \
            -H "Authorization: Bearer $DEPLOYMENT_TOKEN"
      
      - name: Create PR for documentation
        run: |
          git config user.name "github-actions"
          git config user.email "github-actions@users.noreply.github.com"
          git checkout -b rotate-key-$(date +%Y-%m-%d)
          echo "Rotated SECRET_KEY on $(date)" >> ROTATIONS.md
          git add ROTATIONS.md
          git commit -m "chore: document SECRET_KEY rotation"
          gh pr create --title "Document SECRET_KEY rotation" \
            --body "Automatic monthly key rotation completed"

Best Practices & Security Tips

Best Practice 1: Never Hardcode Secrets

# ❌ NEVER DO THIS
SECRET_KEY = 'django-insecure-abc123xyz-hardcoded'  # Don't commit!

# ✅ DO THIS
SECRET_KEY = os.getenv('SECRET_KEY')
if not SECRET_KEY:
    raise ImproperlyConfigured("Set SECRET_KEY environment variable")

Best Practice 2: Use Secrets Management System

  • AWS Secrets Manager: For AWS deployments
  • HashiCorp Vault: Multi-environment support
  • Azure Key Vault: For Azure deployments
  • Google Secret Manager: For GCP deployments
  • 1Password/LastPass: Team-shared credentials
# Example with AWS Secrets Manager
import boto3
import json

def get_secret(secret_name):
    session = boto3.session.Session()
    client = session.client(
        service_name='secretsmanager',
        region_name='us-east-1'
    )
    
    try:
        response = client.get_secret_value(SecretId=secret_name)
        return json.loads(response['SecretString'])
    except Exception as e:
        raise ValueError(f"Could not retrieve secret: {e}")

# settings.py
secret = get_secret('django/production')
SECRET_KEY = secret['SECRET_KEY']
SECRET_KEY_FALLBACKS = secret.get('SECRET_KEY_FALLBACKS', [])

Best Practice 3: Rotate Regularly

  • Development: Every month (less critical)
  • Staging: Every month
  • Production: Every 3 months (minimum quarterly)
See also  How to add to manytomany field in Django

Best Practice 4: Audit Secret Access

# Log who accesses secrets
import logging

logger = logging.getLogger('security')

class SecretKeyMiddleware:
    """Log access to SECRET_KEY"""
    def __init__(self, get_response):
        self.get_response = get_response
    
    def __call__(self, request):
        # Log SECRET_KEY access (careful with this!)
        if hasattr(request, 'session'):
            logger.info(f"Session accessed by {request.user}")
        
        response = self.get_response(request)
        return response

Best Practice 5: Use Strong Keys

# ✅ Strong key (50 characters, base64-encoded)
secrets.token_urlsafe(50)
# Example: KrMfcjtXuA9RL5voYPGjpY79h_mNqK8_vL2Rd5xYz...

# ❌ Weak key (too short, predictable)
'django-insecure-abc123'

# Key requirements:
# - At least 50 characters
# - Cryptographically random
# - URL-safe characters
# - Unique per environment

Best Practice 6: Implement Secrets Scanning

# .pre-commit-config.yaml
repos:
  - repo: https://github.com/Yelp/detect-secrets
    rev: v1.4.0
    hooks:
      - id: detect-secrets
        args: ['--baseline', '.secrets.baseline']

  - repo: https://github.com/gitguardian/ggshield
    rev: v1.22.0
    hooks:
      - id: ggshield
        language: python
        stages: [commit]

Best Practice 7: Monitor SECRET_KEY Usage

# alerts.py
from django.core.signals import setting_changed
from django.dispatch import receiver
import logging

logger = logging.getLogger('security')

@receiver(setting_changed)
def log_setting_changes(sender, setting, **kwargs):
    """Alert if SECRET_KEY changes"""
    if setting == 'SECRET_KEY':
        logger.warning(
            f"SECRET_KEY changed. If not expected, "
            f"investigate immediately."
        )

Security Checklist

Django SECRET_KEY Security Checklist
✓ SECRET_KEY stored in environment variable, not code
✓ .env file in .gitignore
✓ Different SECRET_KEY for each environment
✓ SECRET_KEY at least 50 characters
✓ Generated using secrets.token_urlsafe() or Django's built-in
✓ SECRET_KEY_FALLBACKS configured for safe rotation
✓ Automated rotation scheduled (quarterly minimum)
✓ Secrets management system in use (AWS/Vault/etc)
✓ Pre-commit hook for secrets detection
✓ Audit logging enabled for secret access
✓ Team trained on secret management
✓ Incident response plan in place
✓ Regular security audits scheduled
            

Summary: Mastering Django SECRET_KEY Rotation

You now understand:

  • Why: Prevent unauthorized access and maintain security compliance
  • What breaks: Sessions, CSRF tokens, password reset links become invalid
  • How (safe way): Use SECRET_KEY_FALLBACKS for graceful rotation
  • Best practices: Environment variables, secure generation, regular rotation
  • Emergency response: Immediate steps if compromise is suspected
  • Automation: Make rotation routine and effortless

Golden Rules:

  • 🔑 Never commit SECRET_KEY to version control
  • 🔄 Rotate every 3 months minimum (quarterly)
  • 🔐 Use SECRET_KEY_FALLBACKS for safe rotation
  • ⚡ Automate rotation in CI/CD pipeline
  • 🚨 Have emergency plan for compromised keys

Start implementing these practices today to protect your Django application and users.

Ready to secure your application? Start by moving your SECRET_KEY to environment variables today. That single change eliminates a major security risk.