如何用Nginx的proxy_pass正确转发Django请求

文章导读
Django开发服务器不适合直接对外服务,生产环境通常用Nginx做反向代理。典型场景包括:需要处理大量静态文件、需要SSL终止、需要负载均衡到多个Django实例,或者希望利用Nginx的缓存和限流功能。如果只是简单的内部测试,也可以直接用proxy_pass转发到runserver端口,但不建议用于生产。
📋 目录
  1. 场景:什么时候该用Nginx转发Django
  2. 基础配置:nginx.conf里怎么写
  3. 容易忽略的头信息设置
  4. 静态文件和媒体文件:让Nginx直接处理
  5. WebSocket和长连接:升级协议
  6. 调试和日志:确认转发是否按预期工作
A A

场景:什么时候该用Nginx转发Django

Django开发服务器不适合直接对外服务,生产环境通常用Nginx做反向代理。典型场景包括:需要处理大量静态文件、需要SSL终止、需要负载均衡到多个Django实例,或者希望利用Nginx的缓存和限流功能。如果只是简单的内部测试,也可以直接用proxy_pass转发到runserver端口,但不建议用于生产。

基础配置:nginx.conf里怎么写

最简单的配置是在server块中添加一个location:

server {
    listen 80;
    server_name example.com;
    location / {
        proxy_pass http://127.0.0.1:8000;
    }
}

注意这里proxy_pass的目标地址是Django实际监听的地址,比如Gunicorn或uWSGI。如果Django跑在Unix socket上,可以写成proxy_pass http://unix:/tmp/gunicorn.sock。这个配置跑起来后,访问Nginx的80端口就会将请求转发到Django,但有几项关键的头信息需要额外设置,否则Django无法获知原始请求的细节。

容易忽略的头信息设置

Django依赖request.META中的HTTP头来识别客户端IP和协议。如果不设置,Django获取到的所有客户端IP都是127.0.0.1,且认为所有请求都是HTTP。需要在location中添加:

proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;

同时,Django的settings.py中需要配置USE_X_FORWARDED_HOST = True(如果使用Host头)和SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')(如果启用HTTPS)。注意这些设置会改变Django对安全性的判断,比如is_secure()的结果。还有,如果Nginx本身没有做SSL终止,但上游的Django需要知道客户端是否通过HTTPS访问,可以手动设置proxy_set_header X-Forwarded-Proto $scheme;,但前提是Nginx正确识别了$scheme变量。

静态文件和媒体文件:让Nginx直接处理

Django处理静态文件效率较低,应在Nginx中独立配置location,让Nginx直接返回文件,避免请求到达Django。

如何用Nginx的proxy_pass正确转发Django请求
location /static/ {
    root /var/www/example.com;
}
location /media/ {
    root /var/www/example.com;
}

注意这里的路径要对应Django的STATIC_ROOTMEDIA_ROOT。配置完成后,访问http://example.com/static/css/style.css时Nginx会直接从磁盘读取返回,而不是转发到Django。你可以通过查看Nginx的access日志确认这些请求没有被proxy_pass处理:如果日志中upstream字段为空,说明请求被root直接处理了。

WebSocket和长连接:升级协议

如果Django项目使用了Channels或某些需要WebSocket的库,Nginx需要正确升级协议。在location中添加:

proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";

否则WebSocket握手会失败。注意proxy_http_version设置为1.1是必须的,因为1.0不支持Upgrade头。还要确认Django的ASGI服务器(如Daphne、Uvicorn)确实监听在对应端口。测试WebSocket可以用wscat或者浏览器的开发者工具观察网络连接状态。

调试和日志:确认转发是否按预期工作

配置完成后,通过curl测试是最直接的验证方式:

curl -I http://example.com/

查看响应头中的Server字段,如果是nginx说明Nginx处理了;查看X-Forwarded-For头是否包含你的真实IP。同时观察Django的日志,看请求是否到达。如果遇到502错误,检查Django后端是否正常运行,防火墙是否放行。如果遇到404,检查location是否匹配错误。Nginx的error日志默认在/var/log/nginx/error.log,access日志在/var/log/nginx/access.log。看到upstream prematurely closed connection之类的信息,通常意味着Django某处崩溃或超时。可以先尝试注释掉proxy_set_header的复杂项,用最小配置看能否工作,再逐步加回头信息。