总是得到 http:// 而非 https:// 的原因,以及通过 Nginx 与 Django 两端配置来彻底解决该问题的方案。
摘要
在 Nginx 反向代理环境下,Django 默认 无法 识别客户端与代理之间的 HTTPS 连接,因而 HttpRequest.scheme 始终返回 "http",导致模板中依赖 {{ request.scheme }} 构建的 URL 默认使用 HTTP 协议。本文通过两步配置——在 Nginx 中传递 X-Forwarded-Proto 头部,以及在 Django settings.py 中设置 SECURE_PROXY_SSL_HEADER,使 Django 将该头部映射为 HTTPS 请求,从而正确生成以 https:// 开头的链接。
问题描述
<li>
<i class="iconfont icon-image mr-2"></i>
Logo:
<a href="{% static 'avatar.jpg' %}" target="_blank">
{{ request.scheme }}://{{ request.get_host }}{% static 'avatar.jpg' %}
</a>
</li>
- 现象:即使浏览器地址栏已显示
https://yys.zone,模板渲染出的链接仍是http://yys.zone/static/avatar.jpg。 - 根本原因:
- Django 的
HttpRequest.scheme属性依据request.is_secure()来判断当前请求是否为 HTTPS,但该判断依赖于WsgiRequest.META['HTTP_X_FORWARDED_PROTO'](或类似头部)指示的协议类型,而默认情况下并未传递该头部。 - Nginx 与 Django 间的通信多为 HTTP,Nginx 未告知后端当前连接为 HTTPS,导致
request.is_secure()返回False,进而request.scheme == 'http'。
- Django 的
解决方案
1. 在 Nginx 配置中传递 X-Forwarded-Proto
在你的反向代理配置(如 /etc/nginx/conf.d/yys.conf)中,将 location / 段修改为:
location / {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_redirect off;
proxy_pass http://127.0.0.1:8000;
}
proxy_set_header X-Forwarded-Proto $scheme;会将客户端请求的协议(http或https)传递给后端。proxy_redirect off;可防止 Nginx 修改后端返回的重定向 URL,确保 Django 的重定向不被干扰。
补充:在多级代理场景下,可借助
map指令保留已有头部并避免覆盖:map $http_x_forwarded_proto $thescheme { default $scheme; https https; } proxy_set_header X-Forwarded-Proto $thescheme;该做法可在保留原有
X-Forwarded-Proto的基础上追加或回退至正确协议。
2. 在 Django 中启用 SECURE_PROXY_SSL_HEADER
在 Django 项目的 settings.py 中添加:
# 告诉 Django 信任由上游代理设置的 X-Forwarded-Proto 头部
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
- 该设置会使当
request.META['HTTP_X_FORWARDED_PROTO'] == 'https'时,request.is_secure()返回True,从而request.scheme == 'https'。
验证方法
- 浏览器测试
- 访问
https://yys.zone,点击页面中的“查看头像”链接,确认 URL 已为https://yys.zone/static/avatar.jpg。
- 访问
- 开发者工具
- 打开 Network 面板,查看请求头部中的
X-Forwarded-Proto,应为https。
- 打开 Network 面板,查看请求头部中的
- 后端调试
如果输出def debug_view(request): print(request.is_secure(), request.scheme) # True https ...True, 'https',说明配置生效。
配置示例汇总
# HTTP 重定向到 HTTPS(方案 A)
server {
listen 80;
server_name yys.zone www.yys.zone;
location /.well-known/acme-challenge/ {
root /var/www/html;
}
location /static {
alias /home/blog/static;
}
location / {
return 301 https://$host$request_uri;
}
}
# HTTPS 主服务
server {
listen 443 ssl;
server_name yys.zone www.yys.zone;
ssl_certificate /etc/letsencrypt/live/yys.zone/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/yys.zone/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
location /static {
alias /home/blog/static;
}
location / {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_redirect off;
proxy_pass http://127.0.0.1:8000;
}
}
# settings.py
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
通过上述配置,Django 在生成模板链接时,{{ request.scheme }} 将正确输出 https,从而避免在 HTTPS 环境下生成不安全的 HTTP 链接。若有更多部署或安全相关问题,欢迎进一步交流!
评论区(0 条)
发表评论⏳ 加载编辑器…