结论是问题出在Nginx配置未透传或未正确信任X-Forwarded-For头:必须在location块中添加proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for和X-Real-IP $remote_addr,并启用realip模块配合set_real_ip_from、real_ip_header X-Forwarded-For及real_ip_recursive on,否则Django无法获取真实IP。

直接说结论:问题不在 Django 代码里,而在 Nginx 配置没传头、或传了但没配信任链;Django 拿不到真实 IP,90% 是因为 X-Forwarded-For 根本没到它手里,或者到了但被当成不可信数据丢弃了。
nginx 必须显式设置 X-Forwarded-For 和 X-Real-IP
Nginx 默认不往 upstream 传递任何客户端 IP 相关 header。哪怕你后端代码写得再严谨,request.META.get('HTTP_X_FORWARDED_FOR') 也永远是 None。
-
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;—— 这行必须加在location块里,不是 server 级别;$proxy_add_x_forwarded_for会追加当前$remote_addr,而不是覆盖 -
proxy_set_header X-Real-IP $remote_addr;—— 多一层保险,部分中间件(如某些限流组件)只认这个头 - 别用
$http_x_forwarded_for替代$proxy_add_x_forwarded_for:前者是客户端原始头,可能被伪造;后者才是 Nginx 安全拼接后的可信链 - 如果 Nginx 前还有 CDN(如 Cloudflare),确保 CDN 本身也在请求头里透传
X-Forwarded-For,否则 Nginx 收到的就是空
Django 不能无脑取 HTTP_X_FORWARDED_FOR 的第一个 IP
很多代码直接写 xff.split(',')[0].strip(),这在多层代理下极危险——Cloudflare 把真实 IP 放最后,Nginx 默认追加在末尾,取第一个反而拿到的是中间某跳代理的内网地址(比如 10.0.1.5)。
- 正确做法是逆序遍历:
xff.split(',')[-1].strip(),取最后一个非私有地址 - 必须过滤私有地址段:
127.0.0.0/8、10.0.0.0/8、172.16.0.0/12、192.168.0.0/16、::1等,否则可能把代理内网 IP 当成用户 IP -
REMOTE_ADDR在直连时最准,但在反代场景下只能作为兜底——它可能是 Nginx 所在机器的内网 IP,不是用户真实出口
nginx real_ip 模块配置不当会导致 REMOTE_ADDR 失效
如果你启用了 ngx_http_realip_module(常见于日志记录真实 IP 或配合限流中间件),但没配对,REMOTE_ADDR 就会被强制替换成不可信来源,反而更糟。
立即学习“Python免费学习笔记(深入)”;
-
set_real_ip_from必须精确填写上游可信代理的 IP 或 CIDR(如10.0.0.0/8、172.16.0.0/12),不能写0.0.0.0/0——否则任意请求都能伪造X-Real-IP -
real_ip_header X-Forwarded-For;和real_ip_recursive on;要配套使用:前者指定源字段,后者才能递归剥掉可信代理的 IP,找到最外层真实地址 - 如果没启用 realip 模块,就别在配置里写
real_ip_header,Nginx 会静默忽略,但容易误导排查方向
测试时用 curl -H "X-Forwarded-For: 1.2.3.4" 容易误判
本地用 curl 手动加头测试,看似能拿到 1.2.3.4,但这恰恰掩盖了真实问题——生产环境的 X-Forwarded-For 是由最外层代理(CDN/Ingress)生成并签名的,你手动加的头在启用了 set_real_ip_from 的 Nginx 里会被直接丢弃。
- 真验证,得从公网发起请求,抓 Nginx access log 看
$http_x_forwarded_for字段有没有值 - 在 Django view 里打印
request.META全量内容,确认HTTP_X_FORWARDED_FOR和REMOTE_ADDR的实际值,比猜靠谱得多 - 注意:Django 的
request.META键名全是大写加下划线,HTTP_X_FORWARDED_FOR不是http_x_forwarded_for,大小写敏感
最常被忽略的一点:Nginx 配置改完必须 nginx -t && systemctl reload nginx,光改文件不重载,等于没改;而 Django 代码里那个 get_client_ip 函数,再完美也救不了没传过来的头。


















