配置 Django 与 Nginx:正确处理 HTTPS 请求的指南
总是得到 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 链接。若有更多部署或安全相关问题,欢迎进一步交流!
本文作者: 永生
本文链接: https://yys.zone/detail/?id=406
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明出处!
发表评论
评论列表 (0 条评论)
暂无评论,快来抢沙发吧!