security

Django Security Checklist

A comprehensive security checklist for Django applications in production. Covers HTTPS, CSRF, XSS, SQL injection, authentication hardening, headers, secrets, dependencies, and audit practices.

⏱ 14 min read production
Security audit terminal showing Django deployment check results

Security in Django is not a feature you bolt on at the end. The framework provides strong defaults, but those defaults only protect you if you configure them correctly and avoid the patterns that bypass them. This guide is the security checklist I run through before every production deployment: HTTPS enforcement, CSRF and XSS protections, SQL injection prevention, authentication hardening, security headers, secret management, dependency auditing, and the Django check --deploy command that catches common misconfigurations. For a broader security perspective, see the Security hub.

Django’s security track record is strong. The framework handles most common web vulnerabilities automatically when used correctly. The mistakes that lead to breaches are almost always configuration oversights or developers deliberately bypassing protections.

HTTPS everywhere

Every production Django site must run on HTTPS. Configure Django to enforce it:

SECURE_SSL_REDIRECT = True
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')

Enable HSTS to tell browsers to always use HTTPS:

SECURE_HSTS_SECONDS = 31536000  # 1 year
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True

If you are behind a load balancer or reverse proxy, SECURE_PROXY_SSL_HEADER ensures Django recognizes HTTPS requests correctly. Without it, redirect loops occur because Django sees all traffic as HTTP.

CSRF protection

Django’s CSRF middleware is enabled by default. Keep it that way. Every POST form must include {% csrf_token %}:

<form method="post">
    {% csrf_token %}
    {{ form.as_p }}
    <button type="submit">Save</button>
</form>

For AJAX requests, include the CSRF token in headers:

const csrfToken = document.querySelector('[name=csrfmiddlewaretoken]').value;
fetch('/api/data/', {
    method: 'POST',
    headers: {
        'X-CSRFToken': csrfToken,
        'Content-Type': 'application/json',
    },
    body: JSON.stringify(data),
});

Set CSRF_TRUSTED_ORIGINS for Django 4.0+:

CSRF_TRUSTED_ORIGINS = ['https://prodjango.com']

Never disable CSRF protection, even for API endpoints. Use @csrf_exempt only for genuinely stateless webhook receivers with their own authentication.

XSS prevention

Django auto-escapes template variables by default. This prevents most XSS attacks:

<!-- Safe: automatically escaped -->
<p>{{ user_input }}</p>

<!-- Dangerous: bypasses escaping -->
<p>{{ user_input|safe }}</p>

Rules for XSS prevention:

  • Never use |safe or mark_safe() on user-provided content
  • Sanitize rich text input with a library like bleach before storage
  • Set Content-Security-Policy headers to restrict script sources
  • Use httponly cookies so JavaScript cannot access session tokens

SQL injection protection

Django’s ORM parameterizes all queries automatically, preventing SQL injection. But you can still introduce vulnerabilities with:

# DANGEROUS: string interpolation in raw SQL
cursor.execute(f"SELECT * FROM users WHERE email = '{email}'")

# SAFE: parameterized query
cursor.execute("SELECT * FROM users WHERE email = %s", [email])

Rules:

  • Never use f-strings or .format() in raw SQL
  • Always use parameterized queries with %s placeholders
  • Use the ORM for standard CRUD operations
  • Review any extra(), raw(), or cursor.execute() calls carefully

Security headers

Configure all security-relevant headers:

# settings/production.py
SECURE_CONTENT_TYPE_NOSNIFF = True
SECURE_BROWSER_XSS_FILTER = True
X_FRAME_OPTIONS = 'DENY'

Add a Content Security Policy through middleware or your web server:

Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self';

Set Referrer-Policy and Permissions-Policy in your web server configuration or through Django middleware. The production settings guide covers header configuration in more detail.

Secure all cookies:

SESSION_COOKIE_SECURE = True
SESSION_COOKIE_HTTPONLY = True
SESSION_COOKIE_SAMESITE = 'Lax'
CSRF_COOKIE_SECURE = True
CSRF_COOKIE_HTTPONLY = True

SECURE ensures cookies are only sent over HTTPS. HTTPONLY prevents JavaScript access. SAMESITE protects against cross-site request attacks.

Secret management

  • Generate SECRET_KEY with at least 50 random characters
  • Never commit secrets to version control
  • Use environment variables or a secrets manager
  • Rotate keys if they are ever exposed
  • Use different secrets for different environments
import os
SECRET_KEY = os.environ['DJANGO_SECRET_KEY']

Dependency auditing

Vulnerable dependencies are a common attack vector. Audit regularly:

pip install pip-audit
pip-audit

Run pip-audit in CI to catch known vulnerabilities before deployment. Subscribe to Django’s security mailing list for framework-level advisories.

Update Django promptly when security releases ship. Django’s security team follows responsible disclosure practices and provides clear upgrade guidance.

Django’s deployment checklist

Django includes a built-in deployment checker:

python manage.py check --deploy

This reports common security misconfigurations: missing HSTS, insecure cookies, DEBUG = True, and more. Fix every warning before deploying.

File upload security

  • Validate file types by content, not just extension
  • Set FILE_UPLOAD_MAX_MEMORY_SIZE and DATA_UPLOAD_MAX_MEMORY_SIZE
  • Never serve uploaded files through Django in production
  • Store uploads outside the web root
  • Scan uploads for malware in high-risk applications

Rate limiting

Protect login endpoints and sensitive forms from brute-force attacks:

# Using django-ratelimit
from django_ratelimit.decorators import ratelimit

@ratelimit(key='ip', rate='5/m', method='POST', block=True)
def login_view(request):
    ...

Rate limiting on login prevents credential stuffing. Apply it to password reset and registration endpoints as well.

Frequently asked questions

Is Django secure by default? Django provides strong security defaults, but configuration matters. An unconfigured production deployment with DEBUG = True and no HTTPS is not secure. Run check --deploy and follow this checklist.

How do I handle security for REST APIs? The same principles apply. Use token or session authentication, enforce HTTPS, validate input, parameterize queries, and set appropriate CORS headers. Django REST Framework provides additional security utilities.

Should I use a WAF (Web Application Firewall)? A WAF adds defense in depth but should not be your primary security layer. Fix vulnerabilities in code first. A WAF protects against common attack patterns and gives you time to patch.

How often should I update Django? Apply security releases immediately. Minor version updates (5.1 to 5.2) can wait for your next scheduled maintenance window, but do not delay more than a few weeks. Major version migrations should be planned as a project.