在生产环境中,用 Nginx 代理 Django 出现 403 CSRF 验证错误,根源通常在于 Nginx 未能正确将原始请求的信息(尤其是协议是 HTTP 还是 HTTPS)传递给 Django,导致 Django 的请求校验失败。

要解决这个问题,核心是确保 Django 能“看”到真实的请求,而不是 Nginx 转发后的样子。这需要你同时修改 Django 的 settings.py 和 Nginx 的配置文件。

🐍 第一步:配置 Django (settings.py)

在 Django 的配置文件 settings.py 中,你需要添加或修改以下三个关键配置项。

1. 设置受信任的来源 (CSRF_TRUSTED_ORIGINS)

这是最直接的方法。它明确告诉 Django,来自这些域名的请求是安全的,直接放行通过 CSRF 验证。

# settings.py

# 明确指定所有前端可访问的域名,包括协议
CSRF_TRUSTED_ORIGINS = [
    'https://your-domain.com',  # 你的正式域名
    'https://www.your-domain.com', # 如果存在,也加上带 www 的
    # 'http://test.your-domain.com', # 如果还有其他子域名
]
  • 说明:请将 your-domain.com 替换为你的实际域名。如果你的站点启用了 HTTPS,配置中必须包含 https:// 前缀

  • 注意:虽然设置通配符 * 可能暂时绕过问题,但为了安全考虑,不推荐这样做。

2. 配置代理 SSL 头 (SECURE_PROXY_SSL_HEADER)

如果你的站点配置了 HTTPS,且 Nginx 在内部是通过 HTTP 与 Django 通信的,那么这个设置至关重要。

# settings.py

# 告诉 Django 信任来自 Nginx 的 X-Forwarded-Proto 头信息
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
  • 原理:当 Nginx 将 HTTPS 请求转发给 Django 时,会带上一个自定义的 Header:X-Forwarded-Proto: https。这个设置让 Django 知道,尽管收到的内部请求是 HTTP,但原始请求是安全的 HTTPS,从而通过 CSRF 的检查

  • 重要安全提醒:配置此项后,务必确保 Nginx 是 Django 应用唯一能接触到的外部代理,并配置好防火墙,防止外部请求伪造这个 Header 绕过验证。

3. 启用安全 Cookie (CSRF_COOKIE_SECURE)

此设置强制 CSRF 的 Cookie 只能通过 HTTPS 连接传输,是提升安全性的关键一步。

# settings.py

# 确保 CSRF 的 Cookie 仅通过 HTTPS 发送
CSRF_COOKIE_SECURE = True

说明:启用后,如果 Django 误判了请求为 HTTP,它将不会设置或验证 CSRF Cookie,直接导致 403 错误,这能反向验证你的 SECURE_PROXY_SSL_HEADER 是否配置正确。


🚀 第二步:配置 Nginx (nginx.conf)

接下来,需要在 Nginx 的配置文件的 location 块中,修改或添加一些关键的 proxy_set_header 指令,以确保 Django 能获取到正确的信息。

# /etc/nginx/sites-available/your-site 或 nginx.conf

server {
    listen 80;
    server_name your-domain.com www.your-domain.com;
    # 将 HTTP 请求重定向到 HTTPS
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl;
    server_name your-domain.com www.your-domain.com;

    # SSL 证书配置 (请替换为你的实际路径)
    ssl_certificate     /path/to/your/certificate.crt;
    ssl_certificate_key /path/to/your/private.key;

    # 其他 SSL 配置(如 ssl_protocols 等)...

    location / {
        # 将请求转发给 Django 应用服务器 (如 Gunicorn)
        proxy_pass http://127.0.0.1:8000; # 将端口替换为你应用的端口

        # --- 核心配置:传递原始请求信息 ---
        proxy_set_header Host $host;                    # 传递原始 Host
        proxy_set_header X-Real-IP $remote_addr;       # 传递真实客户端 IP
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;    # 传递原始协议 (http/https)

        # 其他 proxy 配置(可选)
        proxy_redirect off;
        # ... 
    }

    # 静态文件处理 (可选)
    location /static/ {
        alias /path/to/your/staticfiles/;
    }
}

  • proxy_set_header Host $host;:这是非常关键的一步。它确保传递给 Django 的 Host 头是用户访问的原始域名,而不是 Nginx 内部转发的地址

配置完成后,务必运行 sudo nginx -t 测试配置是否正确,然后执行 sudo systemctl reload nginx 或 sudo nginx -s reload 使配置生效。

🩺 第三步:验证与排查

配置完成后,可以通过以下方式验证。

  1. 检查 Django 日志:启用 DEBUG = True 或在日志中查看详细的 403 错误报告,它通常会指明是哪个 Origin 检查失败,这能帮你快速定位问题

  2. 浏览器开发者工具:打开“网络”(Network) 标签页,检查请求的 Cookie 头是否包含 csrftoken,以及 Host 和 Origin 头是否与你的 CSRF_TRUSTED_ORIGINS 设置匹配。

  3. 临时放宽限制(仅用于测试):为验证是否是配置问题,可以临时注释掉 CSRF_COOKIE_SECURE = True,或临时将 CSRF_TRUSTED_ORIGINS = ['*'],如果此时能正常访问,则表明问题确凿在之前的配置上。测试完后必须立即恢复!

希望这份指南能帮你顺利解决问题。在生产环境部署时,请务必仔细核对每一项配置,以确保应用的安全与稳定。