Nginx必须终止SSL,Django不处理HTTPS;ssl_certificate需指向含域名证书+中间证书的fullchain.pem,ssl_certificate_key须为未加密私钥,路径须绝对且www-data可读,同时配置proxy_set_header X-Forwarded-Proto $scheme;与Django的SECURE_PROXY_SSL_HEADER严格匹配。

直接上结论:Django本身不处理HTTPS,必须靠Nginx终止SSL;配错ssl_certificate路径、证书链不全、私钥被加密、或X-Forwarded-Proto头没传,都会导致502、连接重置、Mixed Content警告——不是Django代码问题,是Nginx和Django之间“信任没对齐”。
nginx.conf里ssl_certificate和ssl_certificate_key怎么填才不报502
这是最常卡住的一步。错误不是语法错,而是文件内容/权限/路径三者任一出问题:
-
ssl_certificate必须指向**完整证书链文件**(域名证书 + 中间证书),不能只是domain.crt;常见错误是只放了公钥,漏了ca-bundle.crt,应合并为fullchain.pem -
ssl_certificate_key必须是**未加密的私钥**;如果用openssl rsa -in key.pem -out key-decrypted.pem解密过,就用解密后的那个;别直接用key.pem(它开头是-----BEGIN ENCRYPTED PRIVATE KEY-----) - 路径必须是绝对路径,且Nginx worker进程(通常是
www-data或nginx用户)有读权限:sudo -u www-data cat /etc/nginx/cert/fullchain.pem能读出来才算数 - 两个文件不能合并在一个
.pem里——某些一键脚本会干这事,但Nginx要求严格分离
Django settings.py里SECURE_PROXY_SSL_HEADER为什么总失效
根本原因是Nginx没把HTTPS标识传过来,或者Django没认出来。关键在两端完全匹配:
- Nginx配置里必须加:
proxy_set_header X-Forwarded-Proto $scheme;(不能写死https,否则HTTP请求也带https头) - Django中必须设:
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')(注意大小写,HTTP_前缀+下划线+全大写) - 如果用了CDN(如Cloudflare),
X-Forwarded-Proto可能被覆盖,此时要加SECURE_TRUSTED_PROXIES = ['103.21.244.0/22', '173.245.48.0/20'](填你CDN的真实IP段) -
SECURE_SSL_REDIRECT = True只应在生产环境开;开发时关掉,否则runserver会无限301跳转
Let’s Encrypt续期后网站突然变HTTP或证书过期
certbot续期成功 ≠ 服务自动更新。Nginx不会 reload,旧证书仍被进程持有,新文件已覆盖,结果就是浏览器看到“签名不匹配”或“证书已过期”:
立即学习“Python免费学习笔记(深入)”;
- 续期命令后必须手动触发:
sudo nginx -t && sudo systemctl reload nginx(不是restart,reload更安全) - 检查是否真 reload 成功:
sudo ss -tlnp | grep :443看worker进程PID有没有变 - 如果用Docker部署,不能只改宿主机证书文件——得重建Nginx容器或用
docker cp同步,并nginx -s reload进容器 - HTTP端口务必配301跳转:
return 301 https://$host$request_uri;,否则搜索引擎和用户可能长期停留在HTTP链接
最容易被忽略的点:所有proxy_pass目标(比如http://127.0.0.1:8000)必须是HTTP协议,不能写成https;Nginx终止SSL后,内部通信走明文HTTP是标准做法,强行HTTPS反而会失败。


















