Security Settings
Production Security Critical Proper security configuration is critical for production deployments. Always use:
- ✅ Strong SECRET_KEY (50+ characters)
- ✅ Reverse proxy with SSL/TLS (nginx, Cloudflare, traefik)
- ✅ Specific
security_domainsfor production (never use['*']) - ✅ Environment variables for secrets
Django-CFG provides automatic security configuration based on your domains and environment.
Overview
Environment-Aware Security Django-CFG provides environment-aware security - development is fully open for convenience, production is strict and secure.
How it works:
Development Mode (debug=True or no security_domains):
- ✅ CORS - Fully open (
CORS_ALLOW_ALL_ORIGINS=True) - ✅ ALLOWED_HOSTS - Accepts all (
['*']) - ✅ Docker - Automatic Docker IP support
- ✅ Localhost - All ports allowed
Production Mode (when security_domains specified):
- ✅ ALLOWED_HOSTS - Generated from
security_domains - ✅ CORS - Strict whitelist with credentials
- ✅ CSRF - Trusted origins from domains
- ✅ Security headers - Automatic configuration
- ✅ SSL - Assumes reverse proxy (nginx/Cloudflare)
security_domains Field
The security_domains field is the foundation of security configuration:
from django_cfg import DjangoConfig
class MyConfig(DjangoConfig):
secret_key: str = "your-secret-key"
debug: bool = False
# Production domains (flexible format - Django-CFG normalizes automatically)
security_domains: list = [
"myapp.com", # ✅ No protocol
"https://api.myapp.com", # ✅ With protocol
"admin.myapp.com:8443", # ✅ With port
]
# Development: security_domains optional (CORS fully open by default)
class DevConfig(DjangoConfig):
debug: bool = True
# security_domains not needed - auto-configured for developmentAuto-Generated Settings
From security_domains, Django-CFG automatically generates:
Development Mode (no security_domains):
# CORS fully open
CORS_ALLOW_ALL_ORIGINS = True
CORS_ALLOW_CREDENTIALS = False
# All hosts accepted
ALLOWED_HOSTS = ['*']
# CSRF only for popular dev ports
CSRF_TRUSTED_ORIGINS = [
'http://localhost:3000',
'http://localhost:5173',
# ... 7 popular dev ports
]Production Mode (security_domains specified):
# Strict CORS from security_domains
CORS_ALLOWED_ORIGINS = [
'https://myapp.com',
'https://api.myapp.com',
'https://admin.myapp.com',
]
CORS_ALLOW_CREDENTIALS = True
# Hosts from security_domains + Docker IPs (if detected)
ALLOWED_HOSTS = [
'myapp.com',
'api.myapp.com',
'admin.myapp.com',
# Docker IPs added automatically if /.dockerenv detected
'r"172\.(1[6-9]|2[0-9]|3[0-1])\..*', # Docker range
]
# CSRF from security_domains
CSRF_TRUSTED_ORIGINS = [
'https://myapp.com',
'https://api.myapp.com',
'https://admin.myapp.com',
]SSL/TLS Configuration
Reverse Proxy Best Practice Django-CFG assumes SSL/TLS termination happens at the reverse proxy level (nginx, Cloudflare, traefik, AWS ALB). This is the industry-standard approach.
Default Behavior (Recommended)
class MyConfig(DjangoConfig):
secret_key: str = "your-secret-key"
debug: bool = False
security_domains: list = ["myapp.com"]
# ssl_redirect not specified - Django-CFG defaults to None (disabled)
# SSL handled by reverse proxy (nginx, Cloudflare, etc.)Default behavior:
- ✅
SECURE_SSL_REDIRECT = False- Reverse proxy handles redirects - ✅
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')- Trust proxy headers - ✅
SESSION_COOKIE_SECURE = True- Secure cookies in production - ✅
CSRF_COOKIE_SECURE = True- Secure CSRF tokens
Explicit SSL Redirect (Rare)
Only for Bare Metal
Set ssl_redirect=True ONLY if Django handles SSL directly without a reverse proxy. This is rare in modern deployments.
class MyConfig(DjangoConfig):
secret_key: str = "your-secret-key"
debug: bool = False
security_domains: list = ["myapp.com"]
# Explicit SSL redirect - ONLY if Django handles SSL directly
ssl_redirect: bool = TrueWhen to use:
- ❌ Don’t use with nginx/Cloudflare/traefik (causes redirect loops)
- ✅ Use only for bare metal Django with direct SSL certificates
- ✅ Use only for testing SSL redirects in development
CORS Configuration
Automatic CORS
class MyConfig(DjangoConfig):
secret_key: str = "your-secret-key"
debug: bool = False
security_domains: list = ["myapp.com"]Automatic CORS headers:
CORS_ALLOW_HEADERS = [
'accept',
'accept-encoding',
'authorization',
'content-type',
'dnt',
'origin',
'user-agent',
'x-csrftoken',
'x-requested-with',
]Custom CORS Headers
class MyConfig(DjangoConfig):
secret_key: str = "your-secret-key"
debug: bool = False
security_domains: list = ["myapp.com"]
# Add custom headers
cors_allow_headers: list = [
'x-api-key',
'x-custom-header',
]Result: Merges default headers + custom headers
CORS for API
class MyConfig(DjangoConfig):
secret_key: str = "your-secret-key"
debug: bool = False
security_domains: list = ["api.myapp.com", "myapp.com"]
cors_allow_headers: list = [
'x-api-key',
'authorization',
]Generated settings:
CORS_ALLOW_CREDENTIALS = True
CORS_ORIGIN_WHITELIST = [
'https://api.myapp.com',
'https://www.api.myapp.com',
'https://myapp.com',
'https://www.myapp.com',
'http://localhost:3000',
]Complete Security Example
Production Configuration
# config.py
from django_cfg import DjangoConfig
class ProductionConfig(DjangoConfig):
secret_key: str = "your-production-secret-key-minimum-50-characters"
debug: bool = False
project_name: str = "My Production App"
# Security domains (flexible format - auto-normalized)
security_domains: list = [
"myapp.com", # ✅ No protocol
"https://api.myapp.com", # ✅ With protocol
"admin.myapp.com:8443", # ✅ With port
]
# ssl_redirect: Optional - defaults to None (reverse proxy handles SSL)
# CORS auto-configured from domains
# ALLOWED_HOSTS auto-generatedGenerated security settings:
# ALLOWED_HOSTS (from security_domains + Docker IPs if detected)
ALLOWED_HOSTS = [
'myapp.com',
'api.myapp.com',
'admin.myapp.com',
# Docker IPs added automatically if /.dockerenv exists:
'r"172\.(1[6-9]|2[0-9]|3[0-1])\..*', # Docker bridge
'r"192\.168\..*', # Docker compose
]
# SSL/TLS (assumes reverse proxy)
SECURE_SSL_REDIRECT = False # Reverse proxy handles redirect
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
SECURE_HSTS_SECONDS = 31536000
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True
# CORS (strict whitelist with credentials)
CORS_ALLOWED_ORIGINS = [
'https://myapp.com',
'https://api.myapp.com',
'https://admin.myapp.com',
]
CORS_ALLOW_CREDENTIALS = True
# CSRF (from security_domains)
CSRF_TRUSTED_ORIGINS = [
'https://myapp.com',
'https://api.myapp.com',
'https://admin.myapp.com',
]
# Security headers
SECURE_CONTENT_TYPE_NOSNIFF = True
SECURE_BROWSER_XSS_FILTER = True
X_FRAME_OPTIONS = 'DENY'Development Configuration
# config.py
from django_cfg import DjangoConfig
class DevelopmentConfig(DjangoConfig):
secret_key: str = "dev-secret-key-minimum-50-characters-long-string"
debug: bool = True
project_name: str = "My Dev App"
# security_domains: Optional - not needed in development
# Django-CFG auto-configures for development convenienceGenerated security settings:
# CORS (fully open for development)
CORS_ALLOW_ALL_ORIGINS = True
CORS_ALLOW_CREDENTIALS = False # Must be False with ALLOW_ALL
# ALLOWED_HOSTS (accept everything)
ALLOWED_HOSTS = ['*']
# CSRF (popular dev ports only)
CSRF_TRUSTED_ORIGINS = [
'http://localhost:3000', # React/Next.js
'http://localhost:5173', # Vite
'http://localhost:8080', # Webpack
'http://localhost:4200', # Angular
# ... 7 popular ports
]
# SSL/TLS (disabled in development)
SECURE_SSL_REDIRECT = False
SESSION_COOKIE_SECURE = False
CSRF_COOKIE_SECURE = FalseEnvironment-Specific Security
Using Environment Detection
from django_cfg import DjangoConfig
class MyConfig(DjangoConfig):
secret_key: str = "your-secret-key"
project_name: str = "My App"
@property
def debug(self) -> bool:
return self._environment == "development"
@property
def security_domains(self) -> list:
if self._environment == "production":
return ["myapp.com", "api.myapp.com"]
elif self._environment == "staging":
return ["staging.myapp.com"]
return [] # Development
@property
def ssl_redirect(self) -> bool:
return self._environment in ["production", "staging"]Using YAML Configuration
# config.production.yaml
secret_key: "${SECRET_KEY}" # From environment
debug: false
security_domains:
- myapp.com # ✅ Flexible format
- https://api.myapp.com # ✅ With protocol
- admin.myapp.com:8443 # ✅ With port
# ssl_redirect: Optional - not needed (defaults to reverse proxy mode)
# config.development.yaml
secret_key: "dev-secret-key-minimum-50-chars"
debug: true
# security_domains: Optional - not needed in development
# Django-CFG auto-configures CORS fully open for development# config.py
from django_cfg import load_config
config = load_config() # Loads environment-specific YAMLSecurity Best Practices
Security Checklist Follow all these practices for production security. Skipping any can expose your application to attacks.
SECRET_KEY
1. Always Set Strong SECRET_KEY
Weak SECRET_KEY Never use:
- Default values like “changeme” or “insecure-key”
- Short keys (< 50 characters)
- Predictable patterns or dictionary words
- Keys committed to version control
Consequences:
- Session hijacking
- CSRF token forgery
- Password reset token prediction
- Data tampering
❌ Bad:
secret_key: str = "changeme" # ❌ NEVER DO THIS
secret_key: str = "django-insecure-key" # ❌ Too weak✅ Good:
# Use environment variable
secret_key: str = os.environ["SECRET_KEY"] # ✅ Secure
# Or generate new key:
from django.core.management.utils import get_random_secret_key
print(get_random_secret_key())Key Generation Generate a cryptographically secure key:
python -c "from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())"Environment Variables
2. Use Environment Variables for Secrets
Never Hardcode Secrets Hardcoded secrets in code can be:
- Found in git history (even after deletion)
- Leaked through GitHub search
- Exposed in error messages
- Copied to forks and backups
❌ Bad:
class MyConfig(DjangoConfig):
secret_key: str = "my-actual-secret-key-abc123" # ❌ EXPOSED
database_url: str = "postgresql://user:password@host/db" # ❌ LEAKED✅ Good:
import os
class MyConfig(DjangoConfig):
secret_key: str = os.environ["SECRET_KEY"] # ✅ From environment
database_url: str = os.environ["DATABASE_URL"] # ✅ Secure
# Optional: provide dev fallback
debug: bool = os.getenv("DEBUG", "false").lower() == "true"SSL/TLS
3. Enable SSL at Reverse Proxy
Reverse Proxy SSL Termination Best practice: Handle SSL/TLS at the reverse proxy level (nginx, Cloudflare, traefik, AWS ALB). This is more secure, performant, and flexible than Django-level SSL.
✅ Default (recommended):
class MyConfig(DjangoConfig):
debug: bool = False
security_domains: list = ["myapp.com"]
# ssl_redirect not specified - Django-CFG assumes reverse proxy handles SSLNginx example:
server {
listen 443 ssl http2;
server_name myapp.com;
ssl_certificate /etc/ssl/myapp.com.crt;
ssl_certificate_key /etc/ssl/myapp.com.key;
location / {
proxy_pass http://django:8000;
proxy_set_header X-Forwarded-Proto https; # Django trusts this
proxy_set_header Host $host;
}
}Only Set ssl_redirect=True for Bare Metal
Set ssl_redirect=True ONLY if Django handles SSL certificates directly without a reverse proxy (rare: bare metal without nginx/traefik).
Causes redirect loops with:
- nginx/Apache
- Cloudflare
- AWS ALB/ELB
- traefik
- Any reverse proxy
ALLOWED_HOSTS
4. Restrict ALLOWED_HOSTS
Wildcard ALLOWED_HOSTS
Using ALLOWED_HOSTS = ['*'] allows:
- HTTP Host header attacks
- Cache poisoning
- Email/password reset injection
- Subdomain takeover attacks
❌ Bad:
ALLOWED_HOSTS = ['*'] # ❌ DANGEROUS - allows any domain✅ Good:
class MyConfig(DjangoConfig):
security_domains: list = [
"myapp.com",
"api.myapp.com",
]
# ALLOWED_HOSTS auto-generated with www variants + localhostCORS
5. Configure CORS Properly
Permissive CORS
Using CORS_ALLOW_ALL_ORIGINS = True exposes your API to:
- Cross-site request forgery
- Data theft from other domains
- Unauthorized API access
- Session hijacking
❌ Bad:
CORS_ALLOW_ALL_ORIGINS = True # ❌ DANGEROUS
CORS_ALLOW_CREDENTIALS = True # With wildcard origin = CRITICAL VULNERABILITY✅ Good:
class MyConfig(DjangoConfig):
security_domains: list = [
"myapp.com",
"api.myapp.com",
]
# CORS auto-configured with specific originsCORS + Credentials
Never combine CORS_ALLOW_ALL_ORIGINS = True with CORS_ALLOW_CREDENTIALS = True. This is a critical security vulnerability.
Security Headers Reference
Django-CFG automatically configures security headers based on environment:
Production Headers
SECURE_CONTENT_TYPE_NOSNIFF = True
SECURE_BROWSER_XSS_FILTER = True
X_FRAME_OPTIONS = 'DENY'
SECURE_HSTS_SECONDS = 31536000
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = TrueDevelopment Headers
SECURE_CONTENT_TYPE_NOSNIFF = False
SECURE_BROWSER_XSS_FILTER = False
X_FRAME_OPTIONS = 'SAMEORIGIN'Troubleshooting
ALLOWED_HOSTS Error
Error:
DisallowedHost at /
Invalid HTTP_HOST header: 'newdomain.com'Solution:
class MyConfig(DjangoConfig):
security_domains: list = [
"myapp.com",
"newdomain.com", # Add new domain
]CORS Error
Error:
Access to fetch at 'https://api.myapp.com' from origin 'https://myapp.com'
has been blocked by CORS policySolution:
class MyConfig(DjangoConfig):
security_domains: list = [
"myapp.com",
"api.myapp.com", # Add API domain
]SSL Redirect Loop
Problem: Infinite redirect loop in production
Solution:
class MyConfig(DjangoConfig):
security_domains: list = ["myapp.com"]
ssl_redirect: bool = True
# Add to settings:
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')Django-Axes: Brute-Force Protection
Automatic Brute-Force Protection Django-CFG includes django-axes for automatic brute-force attack protection. It tracks failed login attempts and locks out attackers.
Overview
Django-Axes provides:
- ✅ Failed login tracking - Monitor failed authentication attempts
- ✅ Automatic lockout - Block users/IPs after too many failures
- ✅ Time-based cooldown - Automatic unlock after cooldown period
- ✅ IP + Username tracking - Flexible tracking strategies
- ✅ Proxy/Cloudflare support - Real IP extraction from headers
- ✅ Admin integration - View and manage lockouts in admin panel
Smart Defaults
Django-CFG provides environment-aware defaults for django-axes:
Development Mode:
AXES_ENABLED = True
AXES_FAILURE_LIMIT = 10 # More attempts allowed in dev
AXES_COOLOFF_TIME = 1 # 1 hour lockout
AXES_VERBOSE = True # Detailed loggingProduction Mode:
AXES_ENABLED = True
AXES_FAILURE_LIMIT = 5 # Stricter limit in production
AXES_COOLOFF_TIME = 24 # 24 hours lockout
AXES_VERBOSE = False # Less verbose loggingNo Configuration Required Django-Axes works out of the box with smart defaults. You only need to configure it if you want custom behavior.
Basic Configuration
Use Defaults (Recommended)
from django_cfg import DjangoConfig
class MyConfig(DjangoConfig):
secret_key: str = "your-secret-key"
debug: bool = False
security_domains: list = ["myapp.com"]
# axes: No configuration needed - smart defaults used automaticallyResult: Automatic brute-force protection with environment-aware defaults.
Custom Configuration
from django_cfg import DjangoConfig, AxesConfig
class MyConfig(DjangoConfig):
secret_key: str = "your-secret-key"
debug: bool = False
security_domains: list = ["myapp.com"]
# Custom axes configuration
axes: AxesConfig = AxesConfig(
failure_limit=3, # Only 3 attempts (stricter than default)
cooloff_time=48, # 48 hours lockout (stricter than default)
lockout_template="account/locked.html", # Custom lockout page
)YAML Configuration
secret_key: "${SECRET_KEY}"
debug: false
security_domains:
- myapp.com
# Custom axes configuration
axes:
failure_limit: 3
cooloff_time: 48
lockout_template: "account/locked.html"Configuration Options
Basic Settings
from django_cfg import AxesConfig
axes = AxesConfig(
# Enable/disable axes
enabled=True, # Default: True
# Failure limit (None = auto: 10 dev, 5 prod)
failure_limit=5,
# Cooloff time in hours (None = auto: 1 dev, 24 prod)
cooloff_time=24,
# Lock out after reaching failure limit
lock_out_at_failure=True, # Default: True
# Reset failure count after successful login
reset_on_success=True, # Default: True
)UI/UX Settings
axes = AxesConfig(
# Custom lockout template
lockout_template="account/locked.html", # Default: None (built-in response)
# Custom lockout URL redirect
lockout_url="/account/locked/", # Default: None (built-in response)
)Logging Settings
axes = AxesConfig(
# Verbose logging (None = auto: True dev, False prod)
verbose=True,
# Log access failures for security audit
enable_access_failure_log=True, # Default: True
)IP Whitelist/Blacklist
axes = AxesConfig(
# IP addresses that bypass axes protection
allowed_ips=[
'192.168.1.100', # Office IP
'10.0.0.5', # Admin IP
],
# IP addresses that are always blocked
denied_ips=[
'10.0.0.2', # Known attacker
],
)Proxy/Cloudflare Support
Real IP Extraction Django-Axes automatically extracts real client IPs from proxy headers (nginx, Cloudflare, traefik).
Default Configuration
axes = AxesConfig(
# Number of proxies between client and server
ipware_proxy_count=1, # Default: 1
# Order of headers to extract real IP
ipware_meta_precedence_order=[
'HTTP_X_FORWARDED_FOR', # Cloudflare, nginx, traefik
'HTTP_X_REAL_IP', # Alternative proxy header
'REMOTE_ADDR', # Fallback to direct connection
],
)Cloudflare Configuration
# For Cloudflare
axes = AxesConfig(
ipware_proxy_count=1, # Cloudflare is 1 proxy layer
ipware_meta_precedence_order=[
'HTTP_X_FORWARDED_FOR',
'HTTP_CF_CONNECTING_IP', # Cloudflare-specific header
'REMOTE_ADDR',
],
)Multiple Proxies
# For multiple proxies (e.g., Cloudflare + nginx)
axes = AxesConfig(
ipware_proxy_count=2, # 2 proxy layers
ipware_meta_precedence_order=[
'HTTP_X_FORWARDED_FOR',
'HTTP_X_REAL_IP',
'REMOTE_ADDR',
],
)Custom Lockout Page
Custom Template
1. Create lockout template:
{% extends "base.html" %}
{% block content %}
<div class="lockout-page">
<h1>Account Locked</h1>
<p>
Your account has been locked due to too many failed login attempts.
Please try again in {{ cooloff_time }} hours.
</p>
<p>
If you believe this is a mistake, please contact support.
</p>
</div>
{% endblock %}2. Configure axes:
axes = AxesConfig(
lockout_template="account/locked.html",
)Custom URL Redirect
1. Create lockout view:
from django.shortcuts import render
def account_locked(request):
return render(request, 'account/locked.html', {
'cooloff_time': 24, # Hours
})2. Add URL route:
urlpatterns = [
path('account/locked/', account_locked, name='account_locked'),
]3. Configure axes:
axes = AxesConfig(
lockout_url="/account/locked/",
)Admin Integration
Django-Axes provides admin interface to view and manage lockouts:
# Admin panel automatically includes:
# - Access Attempts (all login attempts)
# - Access Logs (successful logins)
# - Access Failures (failed login attempts)Admin features:
- ✅ View all failed login attempts
- ✅ View locked accounts/IPs
- ✅ Manually unlock accounts
- ✅ View attempt history
- ✅ Search by username/IP
Production Examples
Strict Security
from django_cfg import DjangoConfig, AxesConfig
class MyConfig(DjangoConfig):
secret_key: str = "your-secret-key"
debug: bool = False
security_domains: list = ["myapp.com"]
# Very strict brute-force protection
axes: AxesConfig = AxesConfig(
failure_limit=3, # Only 3 attempts
cooloff_time=72, # 72 hours lockout (3 days)
lockout_template="account/locked.html",
enable_access_failure_log=True,
)Moderate Security
axes: AxesConfig = AxesConfig(
failure_limit=5, # 5 attempts (default production)
cooloff_time=24, # 24 hours lockout
reset_on_success=True,
)Whitelist Admin IPs
axes: AxesConfig = AxesConfig(
failure_limit=5,
cooloff_time=24,
# Whitelist office/admin IPs
allowed_ips=[
'192.168.1.0/24', # Office network
'10.0.0.100', # Admin IP
],
)Cloudflare + nginx Setup
axes: AxesConfig = AxesConfig(
failure_limit=5,
cooloff_time=24,
# Cloudflare + nginx proxy setup
ipware_proxy_count=2,
ipware_meta_precedence_order=[
'HTTP_CF_CONNECTING_IP', # Cloudflare real IP
'HTTP_X_FORWARDED_FOR', # nginx proxy
'HTTP_X_REAL_IP',
'REMOTE_ADDR',
],
)Development vs Production
Development
class DevConfig(DjangoConfig):
debug: bool = True
# axes: Optional - smart defaults provide relaxed settings
# AXES_FAILURE_LIMIT = 10 (more attempts)
# AXES_COOLOFF_TIME = 1 (1 hour)
# AXES_VERBOSE = True (detailed logging)Production
class ProdConfig(DjangoConfig):
debug: bool = False
security_domains: list = ["myapp.com"]
# axes: Optional - smart defaults provide strict settings
# AXES_FAILURE_LIMIT = 5 (stricter)
# AXES_COOLOFF_TIME = 24 (24 hours)
# AXES_VERBOSE = False (less verbose)Monitoring Lockouts
Check Lockout Status
from axes.helpers import get_lockout_response
# Check if IP/username is locked
lockout_response = get_lockout_response(request)
if lockout_response:
# User is locked out
return lockout_responseView Lockout Attempts
from axes.models import AccessAttempt
# Get all lockouts
lockouts = AccessAttempt.objects.filter(failures_since_start__gte=5)
# Get lockouts for specific IP
ip_lockouts = AccessAttempt.objects.filter(ip_address='192.168.1.100')
# Get lockouts for specific username
user_lockouts = AccessAttempt.objects.filter(username='admin')Troubleshooting
False Lockouts
Problem: Legitimate users getting locked out
Solutions:
- Increase failure limit:
axes = AxesConfig(
failure_limit=10, # More lenient
)- Whitelist trusted IPs:
axes = AxesConfig(
allowed_ips=['192.168.1.0/24'], # Office network
)Proxy Issues
Problem: All requests appear to come from proxy IP
Solution: Configure proxy settings correctly
axes = AxesConfig(
# Adjust proxy count
ipware_proxy_count=1, # Or 2 for Cloudflare + nginx
# Add proxy-specific headers
ipware_meta_precedence_order=[
'HTTP_CF_CONNECTING_IP', # For Cloudflare
'HTTP_X_FORWARDED_FOR',
'HTTP_X_REAL_IP',
'REMOTE_ADDR',
],
)Unlock Users
Option 1: Admin Panel
- Go to Admin → Axes → Access Attempts
- Find locked user/IP
- Delete the attempt record
Option 2: Management Command
# Unlock specific username
python manage.py axes_reset username admin
# Unlock specific IP
python manage.py axes_reset ip 192.168.1.100
# Clear all lockouts
python manage.py axes_resetAdvanced Settings
Cache Backend
axes = AxesConfig(
cache_name='default', # Use 'redis' for Redis cache
)Custom Username Field
axes = AxesConfig(
username_form_field='email', # Track by email instead of username
)Security Best Practices
Axes Security Tips
- ✅ Always enable axes in production (
enabled=True) - ✅ Use strict failure limits (3-5 attempts)
- ✅ Configure proxy settings correctly for Cloudflare/nginx
- ✅ Whitelist admin/office IPs to prevent self-lockout
- ✅ Monitor lockout logs for attack patterns
- ✅ Enable access failure logging (
enable_access_failure_log=True)
See Also
- DjangoConfig - Base configuration class
- Configuration Overview - Configuration system overview
- Environment - Environment detection