一、先搞清楚两个角色在干什么
很多人一开始接触部署,会被一堆名词绕得晕头转向。其实你只要把 Gunicorn 和 Nginx 想象成两个分工不同的人,事情就简单多了。
1.1 Gunicorn 是那个埋头干活的
Gunicorn 是一个跑在 Python 应用外面的“服务管家”。你自己写的 Flask 或 Django 代码,本质上是一个函数,等着别人来调用。但 Python 自带的开发服务器(比如 Flask 自带的那个)只能自己玩玩,真到了线上,并发一上来就卡死。Gunicorn 的作用就是用多进程帮你扛住并发请求,把外面来的 HTTP 请求翻译成 Python 能懂的调用方式,再返回结果。
打个比方:你的 Python 应用是个厨师,Gunicorn 就是前厅的传菜员。客人点菜,传菜员拿菜单给后厨,做好再端出去。传菜员多一些,同时能伺候的客人就多一些。
1.2 Nginx 是站在门口的大堂经理
Nginx 不关心你的 Python 代码长什么样,它只负责收“客人”的请求。客人一到,它先看一眼这个请求是想要静态文件(比如图片、CSS),还是想要动态内容。如果是动态内容,它就把请求转给后面真正干活的哥们儿(也就是 Gunicorn)。Nginx 还能顺手做很多杂事:比如限制请求大小、加超时、负载均衡、处理 HTTPS 证书,甚至拦下一些恶意请求。
所以最经典的搭配就是:Nginx 守大门,Gunicorn 在后厨炒菜。两个人各管一摊,配合默契。
二、最常见的几个“别扭”场景
理想很美好,真正配置起来,你会发现一堆小摩擦。下面这几个场景,几乎每个部署过的人都会遇到。
2.1 静态文件谁来管
你的网站肯定有 logo、CSS、JS 这些静态文件。如果你让 Gunicorn 去处理这些文件,那就等于让大厨去端盘子。而且每次请求都要经过 Python 进程,速度慢不说,还白白消耗 CPU。正确的做法是把静态文件全部交给 Nginx,让它直接从硬盘上读出来返回给用户。
但很多新手不知道这个分工,结果 Nginx 和 Gunicorn 都在处理同一个静态文件,混乱得很。
2.2 WebSocket 连接老断开
如果你做了聊天室、实时通知或者在线协作,肯定绕不开 WebSocket。Gunicorn 本身支持 WebSocket,但需要开启特殊的工作模式。而 Nginx 默认用 HTTP/1.0 去代理,对 WebSocket 的长连接不友好,如果你不明确告诉 Nginx“别管这个连接,让它一直通着”,连接就会莫名其妙断掉。
2.3 请求体太大被拒
用户上传文件,明明服务器硬盘够大,但就是传不上去。原因通常是两个地方都限制了请求体大小:Nginx 默认限制 1MB,Gunicorn 默认也不大。两边只要有一个卡住,上传就失败。你得把两边的“胃口”都调大才行。
三、集成时的核心痛点
理解了摩擦点,我们再说深一层,为什么会这样?核心在于两者对连接、进程和协议的理解不一样。
3.1 进程管理不统一
Gunicorn 自己会管理 worker 进程,可以设置数量、超时时间、重启策略。但如果你同时跑了多个服务(比如 API 和后台任务),你还需要一个进程管理工具(比如 systemd 或 Supervisor)来看着它们。Nginx 本身不管这些,它只负责转发。这样就会出现“谁死了都不知道”的情况。Gunicorn 的 worker 崩了,它自己会重新拉起,但如果 Gunicorn 整个进程崩了,你就得靠 systemd 来救了。所以集成时需要把这两层进程管理理清楚。
3.2 超时时间对不上
Gunicorn 默认的 worker 超时时间是 30 秒,也就是说,一个请求处理超过 30 秒,Gunicorn 就直接把 worker 杀掉。Nginx 也有自己的超时时间(比如 proxy_read_timeout 默认 60 秒)。如果你的业务确实需要长时间运行(比如导出大报表),两边超时就要协调好。不然就会出现“Nginx 还在等,Gunicorn 已经把连接掐断了”的情况。
3.3 代理协议要统一
Nginx 转发给 Gunicorn 的时候,可以用 HTTP 协议,也可以用一个叫 “uwsgi” 的协议。但 Gunicorn 一般用 WSGI 协议(也就是 HTTP)。如果两边协议不匹配,请求根本到达不了。通常我们让 Nginx 用 proxy_pass 指令,以 HTTP 方式转发给 Gunicorn 监听的地址。这里最容易犯的错是地址写错,比如 Gunicorn 监听 127.0.0.1:8000,Nginx 却写成了 localhost:8000,结果解析到了 IPv6 上,半天连不上。
四、解决技巧与示例
接下来才是重头戏。我们用一个完整的例子,从零开始把它们“撮合”到一起。为了统一,这里所有代码都基于 Python 3.10 + Flask 2.2 + Gunicorn 20.1 + Nginx 1.18 这套技术栈,后面不再混用其他技术栈。
4.1 先写一个简单的 Flask 应用
我们先建一个最基础的应用文件,命名为 app.py。
# 技术栈:Python / Flask
# 文件名:app.py
# 这个文件里写了一个简单的 Flask 接口,用来验证部署是否成功
from flask import Flask, request, jsonify
# 创建 Flask 应用实例
app = Flask(__name__)
# 定义一个首页接口,返回一段文字
@app.route('/')
def home():
# 直接返回字符串,浏览器会显示出来
return "Hello, 我还在跑着!"
# 定义一个返回 JSON 的接口,用来测试 POST 请求
@app.route('/echo', methods=['POST'])
def echo():
# 接收前端传来的 JSON 数据
data = request.get_json()
# 原样返回,同时带上一个状态码
return jsonify({"你传了什么": data}), 200
# 注意:这里不需要写 app.run(),因为线上用的是 Gunicorn
4.2 用 Gunicorn 把应用跑起来
现在我们要用 Gunicorn 启动这个 Flask 应用。先安装依赖:
# 技术栈:Python / Gunicorn
# 安装 Flask 和 Gunicorn,使用 pip 命令
pip install flask gunicorn
安装完成后,用下面的命令启动:
# 技术栈:Gunicorn 20.1
# 启动命令:-w 代表 worker 数量,-b 代表绑定地址和端口
# 这里用 2 个 worker,监听本机 8000 端口,方便 Nginx 来连
gunicorn -w 2 -b 127.0.0.1:8000 app:app
注意最后面的 app:app,第一个 app 是文件名(去掉 .py 后缀),第二个 app 是 Flask 实例的名字。启动后,你在服务器本机用 curl http://127.0.0.1:8000/ 就能看到返回结果。这证明 Gunicorn 已经把 Python 应用撑起来了。
4.3 写 Nginx 反向代理配置
接下来是 Nginx 的配置。我们把 Nginx 放在 80 端口,把请求转发给 Gunicorn。
# 技术栈:Nginx
# 文件名:/etc/nginx/sites-available/myapp.conf
# 这是一个完整的 server 配置块
# 定义一个上游服务器组,名字叫 gunicorn_backend
# 这里写上 Gunicorn 的监听地址,等等,Nginx 会主动连它
upstream gunicorn_backend {
# 后端 Gunicorn 的地址,必须和 -b 参数保持一致
server 127.0.0.1:8000;
}
server {
# 监听 80 端口,监听所有网卡
listen 80;
# 你的域名,如果没有就填服务器 IP
server_name example.com;
# 处理 /static/ 开头的请求,这些是静态文件
# Nginx 直接从磁盘读取,完全不经过 Python
location /static/ {
# 请改成你自己的静态文件目录
alias /home/user/myapp/static/;
# 缓存静态文件,提升二次访问速度
expires 30d;
}
# 其余所有请求都走下面的代理
location / {
# 把请求转发给上面定义的 gunicorn_backend
proxy_pass http://gunicorn_backend;
# 设置一些转发头,让后端应用能拿到真实 IP
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;
# 关闭 Nginx 对代理响应的缓冲,让结果能尽快返回
# 对长连接和流式响应特别有用
proxy_buffering off;
}
}
要点说明:proxy_pass 可以填 URL 形式,也可以填 upstream 名字。用 upstream 的好处是以后要加多个 Gunicorn 后端时,只需要在这里加一行。
保存配置后,启用并重载 Nginx:
# 技术栈:Nginx
# 把配置文件链接到启用目录
sudo ln -s /etc/nginx/sites-available/myapp.conf /etc/nginx/sites-enabled/
# 测试配置有没有语法错误
sudo nginx -t
# 没有报错就重载 Nginx,让配置生效
sudo systemctl reload nginx
此时你访问 http://服务器IP/,就能看到 Flask 返回的 “Hello, 我还在跑着!”了。到这里,一个最基础的集成已经完成。
4.4 解决“超时时间对不上”的痛点
刚才我们提到,Gunicorn 默认 worker 超时 30 秒,Nginx 默认代理读取超时 60 秒。如果某个接口需要处理 40 秒,Gunicorn 就会先动手杀掉 worker,导致 Nginx 拿不到响应。
解决办法有两个方向:要么把业务改成异步,要么就把两边的超时都调大。调大超时的配置如下。
先看 Gunicorn 的命令行参数:
# 技术栈:Gunicorn
# -t 参数设置 worker 超时时间为 120 秒
gunicorn -w 2 -b 127.0.0.1:8000 -t 120 app:app
如果你用配置文件来管理 Gunicorn,也可以写成一个 gunicorn.conf.py 文件:
# 技术栈:Gunicorn
# 文件名:gunicorn.conf.py
# 这个文件用 Python 语法来配置 Gunicorn 的行为
# 绑定的地址,注意和 Nginx 保持一致
bind = "127.0.0.1:8000"
# 启动多少个 worker,一般设置为 CPU 核数的 2 倍左右
workers = 2
# 每个 worker 的线程数,可以并发处理请求
threads = 2
# worker 超时时间,单位秒,这里设为 120
timeout = 120
# 预加载应用,可以节省内存,但启动会慢一点
preload_app = True
启动时指定配置文件即可:
# 技术栈:Gunicorn
# -c 指定配置文件
gunicorn -c gunicorn.conf.py app:app
接着修改 Nginx 的超时设置。在 location / 里面加上几行:
# 技术栈:Nginx
location / {
# 转发给 Gunicorn 的地址
proxy_pass http://gunicorn_backend;
# 连接后端超时时间:先等 10 秒连接
proxy_connect_timeout 10s;
# 等待后端返回数据的最长时间,这里设为 130 秒,比 Gunicorn 的 120 秒长一点
proxy_read_timeout 130s;
# 发送数据给后端的最长超时时间
proxy_send_timeout 130s;
}
注意一个关键点:Nginx 的 proxy_read_timeout 一定要比 Gunicorn 的 timeout 稍微大一点。否则会出现 Gunicorn 还你等你处理,Nginx 却等得不耐烦先走了的情况。另外,如果你用 systemd 管理 Gunicorn,也要看 systemd 服务里有没有独立的超时设置,别让 systemd 在中间插一杠子。
4.5 让 WebSocket 稳定不掉线
如果你的应用用了 WebSocket,上面的配置还不够。WebSocket 需要从 HTTP 升级到 WebSocket 协议,Nginx 必须明确支持。
完整配置可以单独写一个 location,也可以直接合并到主 location 里。我们演示一个独立的 WebSocket 代理块:
# 技术栈:Nginx
# 这个 location 专门处理 WebSocket 路径,比如 /ws
location /ws/ {
# 转发给 Gunicorn 的 WebSocket 处理地址
proxy_pass http://gunicorn_backend;
# 必须带上这几个头,告诉后端这是 WebSocket 升级
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# 保持长连接,超时设长一点,比如 1 小时
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
}
对应的,Gunicorn 这边也需要使用能处理 WebSocket 的 worker。最简单的办法是把 worker 改成 gevent 或 eventlet,并且安装对应的库。
# 技术栈:Python / Gunicorn
# 安装 gevent 库,用来支持异步 WebSocket
pip install gevent
然后修改 Gunicorn 配置文件:
# 技术栈:Gunicorn
# 文件名:gunicorn.conf.py
# 使用 gevent 作为 worker 类型
worker_class = "gevent"
# 单个 worker 可以处理的并发连接数
worker_connections = 1000
# 超时时间保持 120 秒
timeout = 120
这样,你的 WebSocket 连接就能稳定保持不断线了。注意,如果你的业务非常依赖长连接,最好在 Nginx 和 Gunicorn 之间也启用 keepalive,避免频繁建立新连接。
4.6 负载均衡与多实例部署
当流量变大,一个 Gunicorn 扛不住时,你可以在同一台机器上跑多个 Gunicorn 实例,也可以在多台机器上跑。Nginx 作为入口,可以把请求分发到不同的后端。
比如我们在另一台机器上也部署一套 Gunicorn,IP 是 192.168.1.101,那么 upstream 配置就可以扩展:
# 技术栈:Nginx
# 配置两个后端,Nginx 会轮流分发请求
upstream gunicorn_backend {
# 第一台机器
server 127.0.0.1:8000 weight=3;
# 第二台机器,weight 表示权重,数字越大分到的请求越多
server 192.168.1.101:8000 weight=1;
# 开启 keepalive,Nginx 会一直保持后端的连接池,减少握手开销
keepalive 32;
}
注意:启用 keepalive 后,location 里的 proxy_set_header Connection 要设置为空,否则连接复用会失效。
# 技术栈:Nginx
location / {
# 转发给后端
proxy_pass http://gunicorn_backend;
# 必须设置 HTTP 1.0,才支持 keepalive(默认是 1.0)
proxy_http_version 1.1;
# 清空 Connection 头,让 Nginx 自己管理连接复用
proxy_set_header Connection "";
}
4.7 健康检查与自动摘除
Nginx 自带一个比较简单的健康检查机制,上游服务器如果连续失败,就会被自动标记为不可用。但这依赖 max_fails 和 fail_timeout 参数。
# 技术栈:Nginx
upstream gunicorn_backend {
# 后端 1,连续失败 3 次后暂停用 30 秒
server 127.0.0.1:8000 max_fails=3 fail_timeout=30s;
# 后端 2,同样配置
server 192.168.1.101:8000 max_fails=3 fail_timeout=30s;
}
更精细的健康检查,需要借助 Nginx 的开源版本里没有的功能,但你可以通过定时脚本把不可用的后端从 upstream 里摘掉。比如写一个 shell 脚本,每 10 秒检查一次 Gunicorn 端口,不通就发警报并调用 API 修改 Nginx 配置。不过这属于进阶玩法,大部分中小项目用简单的 max_fails 足够了。
五、应用场景与技术优缺点
5.1 适合什么项目
这套组合最适合传统 Web 应用、REST API、以及带 WebSocket 的轻量级实时应用。特别是你在用 Flask、Django 这类同步框架时,Gunicorn 的多进程模型简单可靠。如果你的项目已经拆分成了微服务,Nginx 也可以作为统一的 API 网关,把不同路径转发到不同的 Gunicorn 实例上。
但如果你做的是高并发、IO 密集型的应用(比如聊天服务),Gunicorn 即便配了 gevent,性能也未必比得上 Go 或 Node.js 写的服务。所以它更适合中小型项目,或者团队里 Python 技术栈占主导的情况。
5.2 优点
- 部署简单:只要装好 Python 依赖,写一个 Nginx 配置就能上线,不需要复杂的容器编排。
- 职责清晰:Nginx 负责入口和静态文件,Gunicorn 专注业务逻辑,出问题容易排查。
- 扩展灵活:Gunicorn 的 worker 数和 Nginx 后端节点都可以随时调,不用改业务代码。
- 稳定性不错:Gunicorn 自带 worker 超时回收,Nginx 自带连接缓冲,两者配合能让应用跑得很稳。
5.3 缺点
- 资源占用偏高:每个 worker 都是独立的 Python 进程,内存开销比较大,比 Go 的协程模型要费内存。
- 配置项繁多:超时、keepalive、缓冲、代理头都容易配错,踩坑成本高。
- 调试不方便:请求经过 Nginx 再转发,看不到真实错误时,得一层层日志翻,有点费劲。
- 性能上限不高:单机情况下,Gunicorn 的并发能力比 Node.js 或 Nginx 静态处理要低不少,如果不懂调优,高峰期很容易把 CPU 跑满。
六、注意事项
集成时最容易栽跟头的地方,我帮你整理了一份“避坑清单”。
第一,地址一定要写对。Gunicorn 绑定 127.0.0.1:8000,Nginx 里就得写 127.0.0.1:8000,不要写 localhost。有些系统里 localhost 会解析到 IPv6 的 ::1,而 Gunicorn 只监听了 IPv4,连接直接失败。
第二,超时要成体系设置。Gunicorn 的 timeout、Nginx 的 proxy_read_timeout、systemd 的 TimeoutStartSec,这三者要形成一个链。最推荐的做法是:Gunicorn 设 120 秒,Nginx 设 130 秒,systemd 设 150 秒。层层预留缓冲。
第三,静态文件千万别让 Gunicorn 碰。不仅慢,而且会占满 worker,导致动态请求排队。一定要在 Nginx 里用 alias 或 root 指到静态目录。如果是在 Django 里,还要记得先运行 collectstatic 收集文件。
第四,日志要开全。Gunicorn 的访问日志和错误日志要分别记录,Nginx 的也需要。出了问题先去 Nginx 的 error.log 看是转发失败还是后端超时,再去 Gunicorn 的日志看应用层有没有崩溃。推荐给 Gunicorn 加上 --access-logfile - 和 --error-logfile -,直接输出到标准输出,方便被 systemd 收集。
第五,防火墙要放行。服务器上如果开了防火墙,记得放行 80/443 端口。否则客户端能访问,但 Nginx 转发给 Gunicorn 却不受影响,因为那是本机内部通信。但如果你跨机器转发,也要确保后端机器的 8000 端口只对 Nginx 所在机器开放,别直接暴露公网。
第六,使用 systemd 管理 Gunicorn。不要手动在终端里启动 Gunicorn,因为一旦 SSH 断开,进程就没了。创建一个 systemd 服务文件:
# 技术栈:systemd
# 文件名:/etc/systemd/system/myapp.service
[Unit]
# 服务描述
Description=My Flask App with Gunicorn
# 在联网后再启动
After=network.target
[Service]
# 运行该应用的用户
User=www-data
# 工作目录,就是 app.py 所在目录
WorkingDirectory=/home/user/myapp
# 启动命令,使用配置文件
ExecStart=/usr/local/bin/gunicorn -c gunicorn.conf.py app:app
# 如果进程崩了,总是重启
Restart=always
# 重启间隔 3 秒
RestartSec=3
[Install]
# 开机自启
WantedBy=multi-user.target
启用并启动服务:
# 技术栈:systemd
# 重新加载 systemd 配置
sudo systemctl daemon-reload
# 启用开机自启
sudo systemctl enable myapp
# 启动服务
sudo systemctl start myapp
# 查看运行状态
sudo systemctl status myapp
第七,动态修改上游配置要谨慎。如果你想手动改 Nginx 配置来添加后端,改完一定要先 nginx -t 测试,再 reload。把配置改错了还强行 reload,可能会导致 Nginx 直接拒绝启动,网站瞬间全挂。
七、文章总结
Gunicorn 和 Nginx 的集成,本质上就是一场“前台接待”和“后厨团队”的磨合。Nginx 替你挡住复杂的网络环境,把干净的请求交给 Gunicorn;Gunicorn 把 Python 应用变成一台能扛住真实流量的服务。两者各有各的脾气,但只要理解了各自的分工,再针对超时、静态文件、WebSocket 这几个关键点做出正确配置,你就能收获一个稳定、高效、好维护的部署环境。
在实际项目里,我并不建议一上来就上 Kubernetes 或者 Docker Compose 那一大堆东西。先用好 Nginx + Gunicorn 这套经典组合,把一个应用跑到稳稳当当,再慢慢扩展架构,这才是最务实的学习路径。希望今天聊的这些细节,能让你少踩几个坑,部署的时候心里更有底。
评论
围绕“Python Web应用部署中,Gunicorn与Nginx反向代理的集成痛点及解决技巧”参与讨论