Rohan Yeole - Homepage Rohan Yeole

Django Security Checklist: 12 Settings Every Production App Must Have

Apr 9, 20261 min read

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

SettingValue
DEBUGFalse
ALLOWED_HOSTSYour domain(s) only
SECURE_SSL_REDIRECTTrue (unless Nginx handles it)
SECURE_HSTS_SECONDS31536000
SESSION_COOKIE_SECURETrue
SESSION_COOKIE_HTTPONLYTrue
CSRF_COOKIE_SECURETrue
X_FRAME_OPTIONS"DENY"
SECRET_KEYFrom environment variable
Raw SQLAlways parameterized
Login endpointsRate limited
Deployment checkpython 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.

Chat with me on WhatsApp