Just two days after the small Django project I just finished went online, I received a SQL injection/XSS warning from a security scan? After deployment, access is ridiculously slow and key logs cannot be found? This blog organizes the entire process from security configuration to deployment of production-level Django. The pitfalls and core best practices are condensed here.


In this article you will learn

  • Django common security threats and built-in protection functions
  • Security hard configuration for production environment in settings.py
  • Implementation code for input verification, model layer purification, and CSRF/XSS reinforcement
  • Docker+Gunicorn+Postgres lightweight deployment solution
  • Tips on caching and query optimization
  • Basic monitoring and pre-deployment checklist

Django security first

First figure out what to guard against

Before launching the Django application, first keep an eye on these high-frequency vulnerabilities:

  • SQL injection: cannot be avoided, fortunately django ORM automatically prevents it
  • XSS: User input is stored in malicious HTML/JS and rendered out
  • CSRF: induce users to submit fake requests on logged-in sites
  • Click Hijacking: Transparent iframe overlays buttons to steal actions
  • Brute force cracking: try passwords in batches

Django’s own “security shield”

Don’t reinvent the wheel! Let’s open these first:

  • Templates automatically escape XSS
  • Built-in CSRF middleware
  • ORM automatically prevents SQL injection
  • X-Frame-Options anti-clickjacking
  • Extensible user authentication/authorization system

Knock on the blackboard! settings.py production environment hard configuration

# production_settings.py(必须和base分离!!!)
import os
from pathlib import Path
from .base import *

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

# 1. 最核心的配置
DEBUG = False  # 绝对!绝对!绝对!不能是True!!!
SECRET_KEY = os.environ.get("DJANGO_SECRET_KEY")  # 放环境变量,别写代码里
ALLOWED_HOSTS = ["yourdomain.com", "www.yourdomain.com"]  # 别用*,只加生产域名

# 2. HTTPS相关(必须全站HTTPS)
SECURE_SSL_REDIRECT = True
SECURE_HSTS_SECONDS = 31536000  # 强制浏览器用HTTPS访问1年
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
SESSION_COOKIE_HTTPONLY = True  # 防止JS读Session Cookie
CSRF_COOKIE_HTTPONLY = True  # 可选,增强CSRF防护
SESSION_COOKIE_SAMESITE = "Strict"
CSRF_COOKIE_SAMESITE = "Strict"

# 3. 其他安全头部
SECURE_BROWSER_XSS_FILTER = True
SECURE_CONTENT_TYPE_NOSNIFF = True
X_FRAME_OPTIONS = "DENY"

# 4. 数据库(Postgres推荐,必开SSL)
DATABASES = {
    "default": {
        "ENGINE": "django.db.backends.postgresql",
        "NAME": os.environ.get("DB_NAME"),
        "USER": os.environ.get("DB_USER"),
        "PASSWORD": os.environ.get("DB_PASSWORD"),
        "HOST": os.environ.get("DB_HOST"),
        "PORT": "5432",
        "OPTIONS": {"sslmode": "require"},
        "CONN_MAX_AGE": 600,  # 连接池,减少开销
    }
}

# 5. 密码验证(别用默认的短密码!!!)
AUTH_PASSWORD_VALIDATORS = [
    {"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator"},
    {"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", "OPTIONS": {"min_length": 12}},
    {"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator"},
    {"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator"},
]

# 6. 防暴力破解(用django-axes)
INSTALLED_APPS += ["axes"]
AUTHENTICATION_BACKENDS = [
    "axes.backends.AxesStandaloneBackend",
    "django.contrib.auth.backends.ModelBackend",
]
AXES_FAILURE_LIMIT = 5
AXES_COOLOFF_TIME = 1  # 1小时后解锁
AXES_LOCK_OUT_BY_COMBINATION_USER_AND_IP = True

Do not relax the input and model layers

Templates automatically escaping XSS is not enough! It is also necessary to perform double verification + purification at the form layer and model layer. The one recommended by Django is used here.bleachLibrary for handling rich text.

Form layer security

# forms.py
from django import forms
from django.core.validators import RegexValidator
import bleach

class SafeContactForm(forms.Form):
    name = forms.CharField(
        max_length=100,
        validators=[RegexValidator(r"^[a-zA-Z\s]+$", "姓名只能含字母和空格")]
    )
    email = forms.EmailField()
    message = forms.CharField(widget=forms.Textarea)

    # 单独净化message
    def clean_message(self):
        message = self.cleaned_data["message"]
        # 这里只允许纯文本,如果有富文本需求再开allowed_tags
        return bleach.clean(message, tags=[], strip=True)

    # 全局验证垃圾内容
    def clean(self):
        cleaned_data = super().clean()
        message = cleaned_data.get("message", "").lower()
        spam_keywords = ["viagra", "casino", "loan"]
        if any(k in message for k in spam_keywords):
            raise forms.ValidationError("消息包含不安全内容")
        return cleaned_data

Model layer leak repair

# models.py
from django.db import models
from django.core.exceptions import ValidationError
import bleach

class Article(models.Model):
    title = models.CharField(max_length=200)
    content = models.TextField()  # 富文本场景
    author = models.ForeignKey("auth.User", on_delete=models.CASCADE)
    created_at = models.DateTimeField(auto_now_add=True)

    # 模型验证
    def clean(self):
        # 检查标题有没有恶意JS开头
        if self.title.lower().startswith("javascript:"):
            raise ValidationError("标题包含不安全内容")

    # 保存前净化富文本
    def save(self, *args, **kwargs):
        allowed_tags = ["p", "br", "strong", "em", "ul", "ol", "li"]
        allowed_attrs = {}
        self.content = bleach.clean(self.content, tags=allowed_tags, attributes=allowed_attrs, strip=True)
        super().save(*args, **kwargs)

CSRF protection small supplement

The default CSRF middleware already covers form submission, but pay attention to the AJAX situation:

// 前端JS(jQuery为例)
$.ajaxSetup({
    beforeSend: function(xhr, settings) {
        if (!(/^http:.*/.test(settings.url) || /^https:.*/.test(settings.url))) {
            // 只对同域请求加CSRF Token
            xhr.setRequestHeader("X-CSRFToken", getCookie("csrftoken"));
        }
    }
});

// 获取Cookie的工具函数
function getCookie(name) {
    let cookieValue = null;
    if (document.cookie && document.cookie !== '') {
        const cookies = document.cookie.split(';');
        for (let i = 0; i < cookies.length; i++) {
            const cookie = cookies[i].trim();
            if (cookie.substring(0, name.length + 1) === (name + '=')) {
                cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
                break;
            }
        }
    }
    return cookieValue;
}

Lightweight deployment: Docker+Gunicorn

Don't use it directlypython manage.py runserver! That's for development environments, it's unsafe and slow.

Dockerfile

# Dockerfile
FROM python:3.11-slim

WORKDIR /app

# 安装系统依赖(Postgres客户端)
RUN apt-get update && apt-get install -y --no-install-recommends \
    postgresql-client \
    gcc \
    && rm -rf /var/lib/apt/lists/*

# 复制依赖并安装(缓存层优化)
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# 复制应用代码
COPY . .

# 【重要】创建非root用户运行
RUN useradd --create-home --shell /bin/bash app
RUN chown -R app:app /app
USER app

# 收集静态文件
RUN python manage.py collectstatic --noinput

EXPOSE 8000

# 启动Gunicorn
CMD ["gunicorn", "--bind", "0.0.0.0:8000", "--workers", "3", "myproject.wsgi:application"]

docker-compose.yml

No need to install Postgres and Redis separately, start them with one click:

# docker-compose.yml
version: '3.8'

services:
  web:
    build: .
    ports:
      - "8000:8000"
    environment:
      - DJANGO_SETTINGS_MODULE=myproject.production_settings
      - DB_HOST=db
      - DJANGO_SECRET_KEY=your_secure_secret_key_here
      - DB_NAME=myproject
      - DB_USER=postgres
      - DB_PASSWORD=your_secure_db_password_here
    depends_on:
      - db
    volumes:
      - static_volume:/app/staticfiles
      - media_volume:/app/media
    restart: always

  db:
    image: postgres:15-alpine
    environment:
      - POSTGRES_DB=myproject
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=your_secure_db_password_here
    volumes:
      - postgres_data:/var/lib/postgresql/data
    restart: always

  # 可选:Nginx作为反向代理+静态文件服务器
  # nginx:
  #   image: nginx:alpine
  #   ports:
  #     - "80:80"
  #     - "443:443"
  #   volumes:
  #     - ./nginx/conf.d:/etc/nginx/conf.d
  #     - static_volume:/app/staticfiles
  #     - media_volume:/app/media
  #     - ./ssl:/etc/nginx/ssl
  #   depends_on:
  #     - web
  #   restart: always

volumes:
  postgres_data:
  static_volume:
  media_volume:

Performance and monitoring wrap-up

Simple cache optimization

usedjango-redisCache frequently accessed data:

# settings.py(加在生产配置里)
INSTALLED_APPS += ["django_redis"]
CACHES = {
    "default": {
        "BACKEND": "django_redis.cache.RedisCache",
        "LOCATION": "redis://redis:6379/1",
        "OPTIONS": {"CLIENT_CLASS": "django_redis.client.DefaultClient"},
    }
}
# views.py
from django.views.decorators.cache import cache_page

@cache_page(60 * 15)  # 缓存15分钟
def article_list_view(request):
    articles = Article.objects.published().select_related("author")  # 同时优化查询
    return render(request, "articles/list.html", {"articles": articles})

Must-check checklist before deployment (check all the boxes before going online!)

  • DEBUG = False
  • SECRET_KEY and database password are all in environment variables
  • ALLOWED_HOSTS only production domain names
  • Full site HTTPS, security header configured
  • Static files have been collected and the permissions are correct
  • The database has SSL enabled and the connection pool has been added.
  • django-axes has been configured to prevent brute force cracking
  • The log has been configured and the path has write permission.
  • All dependent packages have been updated to the latest security version

Summary and next steps

Security is not something that can be done once and for all. Django and dependent packages must be updated regularly (usepip-auditScan), perform regular security scans of OWASP ZAP; performance must also be continuously monitored, and cache time and queries must be adjusted according to access patterns. Next step you can try:

  • Use Nginx as reverse proxy + static file server (faster than Gunicorn)
  • Apply for a free SSL certificate with Let’s Encrypt
  • Enhance error monitoring with Sentry
  • Handle asynchronous tasks with Celery

I hope this blog can help you deploy your Django project safely and quickly!