Django is secure by default — but only if you configure it correctly. DEBUG=True in production is the most common mistake, but it is far from the only one. Here are 12 security settings every Django production application must have, with the reason each one matters.
1. Turn Off Debug Mode
# settings.py
DEBUG = False
With DEBUG=True, Django shows full stack traces to visitors when an error occurs — including your settings, environment variables, and installed packages. This is useful in development and dangerous in production. Always set DEBUG=False before deploying.
2. Set ALLOWED_HOSTS
ALLOWED_HOSTS = ["rohanyeole.com", "www.rohanyeole.com"]
Without this, Django accepts requests for any domain — enabling HTTP Host header attacks. List only the domains your application should respond to. Use os.environ.get("ALLOWED_HOSTS", "").split(",") to configure it from an environment variable.
3. Enforce HTTPS
SECURE_SSL_REDIRECT = True
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
SECURE_SSL_REDIRECT redirects all HTTP requests to HTTPS at the Django level. If Nginx handles the redirect (common), set SECURE_SSL_REDIRECT = False and only set SECURE_PROXY_SSL_HEADER — which tells Django to trust the Nginx-forwarded protocol header. Setting both causes an infinite redirect loop.
4. Enable HSTS
SECURE_HSTS_SECONDS = 31536000 # 1 year
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True
HTTP Strict Transport Security tells browsers to only connect via HTTPS for the specified duration. Once set, browsers will not attempt HTTP connections to your domain — even if the user types http://. Start with a short value (300 seconds) in staging to verify nothing breaks, then increase to 1 year.
5. Secure Cookies
SESSION_COOKIE_SECURE = True
SESSION_COOKIE_HTTPONLY = True
CSRF_COOKIE_SECURE = True
CSRF_COOKIE_HTTPONLY = True
SECURE = True means cookies are only sent over HTTPS — they will not be transmitted over plain HTTP connections. HTTPONLY = True means JavaScript cannot access the cookie — it mitigates XSS attacks that attempt to steal session tokens.
6. Content Security Policy Headers
Django does not set CSP headers by default. Use django-csp:
pip install django-csp
INSTALLED_APPS += ["csp"]
MIDDLEWARE += ["csp.middleware.CSPMiddleware"]
CSP_DEFAULT_SRC = ("'self'",)
CSP_SCRIPT_SRC = ("'self'", "https://cdn.jsdelivr.net")
CSP_STYLE_SRC = ("'self'", "https://fonts.googleapis.com")
CSP_FONT_SRC = ("'self'", "https://fonts.gstatic.com")
CSP_IMG_SRC = ("'self'", "data:", "https:")
CSP headers tell browsers which sources are allowed to load scripts, styles, images, and fonts. A strict CSP blocks inline scripts — which prevents XSS attacks from executing even if an attacker injects HTML.
7. X-Frame-Options
X_FRAME_OPTIONS = "DENY"
Prevents your pages from being embedded in iframes on other domains — blocking clickjacking attacks. Use "SAMEORIGIN" if you need iframes within your own domain.
8. Keep Secrets Out of Code
import os
SECRET_KEY = os.environ["SECRET_KEY"]
DATABASE_PASSWORD = os.environ["DB_PASSWORD"]
Never commit SECRET_KEY, passwords, API keys, or tokens to version control. Use environment variables. On production servers, set them via systemd EnvironmentFile, Docker secrets, or a secrets manager like HashiCorp Vault. Check your git history — if a secret was ever committed, rotate it immediately.
A .env file pattern for local development:
# .env (add to .gitignore)
SECRET_KEY=your-local-secret-key
DB_PASSWORD=localpassword
# settings.py
from dotenv import load_dotenv
load_dotenv()
SECRET_KEY = os.environ["SECRET_KEY"]
9. SQL Injection Prevention
Django's ORM parameterizes queries automatically — standard ORM usage is safe. The risk comes from raw SQL:
# DANGEROUS — vulnerable to SQL injection
User.objects.raw(f"SELECT * FROM users WHERE name = '{name}'")
# SAFE — parameterized
User.objects.raw("SELECT * FROM users WHERE name = %s", [name])
# Also safe — ORM handles parameterization
User.objects.filter(name=name)
Avoid string interpolation in any SQL query. If you must use raw SQL, always use Django's parameterization (%s placeholders with a separate values list).
10. Validate File Uploads
If your application accepts file uploads, validate the content type server-side — not just the file extension:
import magic # python-magic library
def validate_image(upload):
file_type = magic.from_buffer(upload.read(1024), mime=True)
upload.seek(0)
allowed_types = ["image/jpeg", "image/png", "image/gif", "image/webp"]
if file_type not in allowed_types:
raise ValidationError("Invalid file type.")
File extensions are user-controlled and trivially spoofed. Content-based type detection (using the file's magic bytes) is reliable.
11. Rate Limit Authentication Endpoints
Django does not rate-limit login attempts by default. A brute-force attack can try thousands of passwords without restriction:
# With DRF + SimpleJWT
from rest_framework.throttling import AnonRateThrottle
class LoginRateThrottle(AnonRateThrottle):
rate = "5/minute"
scope = "login"
class LoginView(TokenObtainPairView):
throttle_classes = [LoginRateThrottle]
For session-based auth, django-axes tracks failed login attempts and locks accounts:
pip install django-axes
INSTALLED_APPS += ["axes"]
MIDDLEWARE += ["axes.middleware.AxesMiddleware"]
AUTHENTICATION_BACKENDS = ["axes.backends.AxesStandaloneBackend"]
AXES_FAILURE_LIMIT = 5
AXES_COOLOFF_TIME = 1 # Hours
12. Run Django's Security Check
python manage.py check --deploy
Django's built-in security check scans your settings and reports any obvious misconfigurations. Run it as part of your CI pipeline — before every deployment:
# .github/workflows/django.yml
- name: Security check
run: python manage.py check --deploy
The output lists every security setting that is not configured correctly for production. If it passes cleanly, your baseline is solid.
Summary Checklist
| Setting | Value |
|---|---|
DEBUG | False |
ALLOWED_HOSTS | Your domain(s) only |
SECURE_SSL_REDIRECT | True (unless Nginx handles it) |
SECURE_HSTS_SECONDS | 31536000 |
SESSION_COOKIE_SECURE | True |
SESSION_COOKIE_HTTPONLY | True |
CSRF_COOKIE_SECURE | True |
X_FRAME_OPTIONS | "DENY" |
SECRET_KEY | From environment variable |
| Raw SQL | Always parameterized |
| Login endpoints | Rate limited |
| Deployment check | python manage.py check --deploy |
Security is not a feature you add at the end — it is configuration you set before the first deployment. If you need a Django application built with security in mind from the start, hire me as a Django developer or as a backend developer.