<!DOCTYPE html>
Python Web 应用部署实战:Gunicorn 安装配置、systemd 托管与 Nginx 反向代理调优
写好的 Python Web 应用(Flask、Django、FastAPI)默认自带的开发服务器只能用于本地调试,直接裸奔到公网既慢又不安全。生产环境的标准做法是:Gunicorn 作为 WSGI 应用服务器负责跑 Python 代码,systemd 负责开机自启和崩溃拉起,Nginx 在它前面做反向代理、静态文件服务与 HTTPS 终结。本文从安装到调优,再到 502/504 排错,给出一套完整的部署链路。
一、为什么生产环境要用 Gunicorn 而不是 python app.py
Flask 自带的 app.run()、Django 的 runserver 都是单进程单线程的开发服务器,官方明确不建议用于生产:一次只能处理一个请求,并发稍微上来就排队,而且没有进程隔离,一个请求崩溃整个服务就挂了。
Gunicorn(Green Unicorn)是 Python 生态最主流的 WSGI 服务器:它按"预派生(pre-fork)"模型管理多个 worker 进程,每个 worker 独立处理请求,单个 worker 崩溃会被主进程自动拉起。请求到达后由 worker 调用你的 WSGI 应用对象(Flask 的 app、Django 的 wsgi.application),对应用代码完全无侵入。
浏览器 --> Nginx(80/443) --> Gunicorn(127.0.0.1:8000) --> WSGI 应用 --> 数据库/Redis
Nginx 只负责转发请求和返回静态文件,真正的 Python 代码全部在 Gunicorn 的 worker 里执行。这样静态资源不占用 Python 进程,性能和安全都更可控。
二、安装 Gunicorn:虚拟环境优先
强烈建议在虚拟环境里安装 Gunicorn,避免污染系统 Python 环境,也方便按项目锁定版本。以 Debian/Ubuntu 为例:
# 1. 安装 Python3、虚拟环境工具和项目依赖 apt install -y python3 python3-venv python3-pip # 2. 创建项目目录并初始化虚拟环境 mkdir -p /var/www/myapp cd /var/www/myapp python3 -m venv venv # 3. 激活虚拟环境并安装依赖 source venv/bin/activate pip install -U pip pip install gunicorn flask # 按项目实际依赖安装 # 4. 验证版本 gunicorn --version
如果服务器无法直接访问 PyPI,可以临时换国内镜像源加速:
pip install -i https://mirrors.aliyun.com/pypi/simple/ gunicorn
三、Gunicorn 运行方式:命令行参数与配置文件
最简启动方式:指定 WSGI 入口模块。假设项目里 app.py 定义了 app = Flask(__name__):
# 从 app 模块导入 app 对象,监听本机 8000 端口 gunicorn -w 4 -b 127.0.0.1:8000 app:app
参数多了以后命令行太长,推荐写配置文件 gunicorn.conf.py,Gunicorn 会自动读取同目录下的同名文件,或显式指定:
# /var/www/myapp/gunicorn.conf.py import multiprocessing # 监听地址:只绑内网,由 Nginx 转发进来 bind = "127.0.0.1:8000" # worker 数量:CPU 核数 * 2 + 1 是常见经验值 workers = multiprocessing.cpu_count() * 2 + 1 # worker 类型:sync 是最简单稳定的同步模型 worker_class = "sync" # 单 worker 处理请求的超时秒数,超时会杀掉该 worker 并重启 timeout = 60 # 每个 worker 最多处理 1000 个请求后自动重启(防内存泄漏累积) max_requests = 1000 max_requests_jitter = 100 # 日志 accesslog = "/var/log/gunicorn/access.log" errorlog = "/var/log/gunicorn/error.log" loglevel = "info" # 进程名与文件 name = "myapp" pidfile = "/var/run/gunicorn/myapp.pid"
创建日志目录并启动:
mkdir -p /var/log/gunicorn /var/run/gunicorn gunicorn -c gunicorn.conf.py app:app
四、用 systemd 托管:开机自启与崩溃自动拉起
直接命令行启动的 Gunicorn 会在 SSH 断开时被终止。用 systemd 把它注册成服务,开机自启、崩溃重启、日志统一管理全都交给系统:
# /etc/systemd/system/myapp.service [Unit] Description=Gunicorn instance for myapp After=network.target [Service] # 用专用系统用户运行,别用 root User=www-data Group=www-data WorkingDirectory=/var/www/myapp Environment="PATH=/var/www/myapp/venv/bin" Environment="PYTHONUNBUFFERED=1" ExecStart=/var/www/myapp/venv/bin/gunicorn -c gunicorn.conf.py app:app Restart=always RestartSec=3 [Install] WantedBy=multi-user.target
启动并设置开机自启:
systemctl daemon-reload systemctl enable --now myapp systemctl status myapp
发布新代码后平滑重启(不中断在途请求):
# 优雅重启:先拉起新 worker,再回收旧 worker systemctl reload myapp # 如果改了 systemd 配置或环境变量,用 restart systemctl restart myapp
注意 reload 依赖 Gunicorn 支持平滑重启(默认支持),它发送 HUP 信号给主进程,主进程逐个替换 worker,而不是粗暴全部杀掉。
五、Nginx 反向代理:静态文件分流与安全头
Nginx 配置一个 server 块,把动态请求代理给 Gunicorn,同时自己处理静态文件(图片、CSS、JS),Python 进程就完全不碰这些资源:
# /etc/nginx/sites-available/myapp
server {
listen 80;
server_name myapp.example.com;
# 静态文件由 Nginx 直接返回
location /static/ {
alias /var/www/myapp/static/;
expires 7d;
access_log off;
}
# 其他请求全部转发给 Gunicorn
location / {
proxy_pass http://127.0.0.1:8000;
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;
# 长连接/超时设置,与 Gunicorn 的 timeout 保持协调
proxy_connect_timeout 60s;
proxy_read_timeout 60s;
}
}
启用站点并重载:
ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/ nginx -t && systemctl reload nginx
部署 HTTPS 时,证书续期工具(如 certbot)会在 reload 后自动加 443 跳转,这里不再展开。别忘了在 Gunicorn 侧设置 X-Forwarded-Proto 后,Flask/Django 里要开启代理信任(如 Flask 的 ProxyFix),否则应用拿到的 request.scheme 始终是 http,会导致生成的 URL 和 OAuth 回调错误。
六、性能调优:worker 数量、超时与内存泄漏
1. worker 数量怎么定
同步 worker(sync)每个 worker 同时只能处理一个请求,所以:
# 经验公式:2 * CPU 核数 + 1 # 2 核机器 → 5 个 worker workers = 5
如果应用里有大量 I/O 等待(数据库查询、外部 API 调用),CPU 计算占比低,可以适当加大 worker 数;如果应用是 CPU 密集(计算型任务),worker 数超过核心数反而会因上下文切换变慢。
2. 用 gevent worker 提升 I/O 并发
同步模型下每个 worker 同一时间只能处理一个请求,遇到慢查询会被"卡住"。改用 gevent 协程 worker,一个 worker 内可以并发处理大量 I/O 请求:
pip install gevent # gunicorn.conf.py 中修改 worker_class = "gevent" # 每个 worker 的并发协程数 worker_connections = 1000
注意:gevent 会 monkey-patch 网络库,个别不兼容的库(某些数据库驱动、C 扩展)需要实测验证,生产环境务必先压测再上线。
3. 超时与 max_requests:两个防崩溃保险
timeout 防止某个请求卡死整个 worker;max_requests 防止长期运行的内存泄漏。设置建议:timeout 取接口最慢正常耗时的 3~5 倍;max_requests 1000~2000 是安全区间。日志里出现 Worker timed out 时,先看是不是某接口真的慢,而不是盲目调大超时掩盖问题。
# 快速观察 worker 数量与状态 ps aux | grep gunicorn # 看错误日志中的超时和重启记录 tail -f /var/log/gunicorn/error.log
七、常见排错:502、504 与各种玄学问题
1. Nginx 返回 502 Bad Gateway
502 表示 Nginx 连不上 Gunicorn。按顺序排查:
# 1) Gunicorn 是否在跑 systemctl status myapp # 2) 端口是否在监听(bind 必须与 proxy_pass 一致) ss -tlnp | grep 8000 # 3) 权限问题:worker 用户能否读取项目文件 sudo -u www-data ls /var/www/myapp/app.py # 4) 手动启动看报错 cd /var/www/myapp && sudo -u www-data ./venv/bin/gunicorn -c gunicorn.conf.py app:app
最常见原因:项目目录或代码文件权限是 root,www-data 用户读不了;或者 bind 写成了 0.0.0.0:8000 但防火墙没放行(其实绑 127.0.0.1 就完全不需要放行防火墙)。
2. 504 Gateway Timeout
504 是 Nginx 的 proxy_read_timeout 到点而 Gunicorn 还没返回。两个超时要配合:Nginx 的 proxy_read_timeout 必须大于等于 Gunicorn 的 timeout。否则 Gunicorn 还在跑(可能 60 秒内能完成),Nginx 60 秒先放弃了。另外注意 timeout 到点后 Gunicorn 会杀掉 worker,产生 Worker timed out 日志,此时前端表现为"请求突然中断"而不是 504。
3. 502 时日志却显示 worker 正常
检查是否端口冲突:另一个进程(比如别的项目)占了 8000。用 ss -tlnp | grep 8000 看进程名。还有可能是 systemd 服务启动了两次(daemon-reload 后旧的还在),先 systemctl stop 再 start。
4. 访问 /static 404
alias 路径写错最常见的坑:location /static/ { alias /var/www/myapp/static/; } 的 alias 路径末尾斜杠不能省,否则 Nginx 会把 URL 路径直接拼到 alias 后面导致路径错乱。用 nginx -T | grep -A 5 static 检查最终配置。
5. 修改代码后不生效
Gunicorn 默认不监听代码变化(那是开发模式)。发布流程是:git pull / 上传代码 → systemctl reload myapp。如果项目里用了 --reload 参数跑在后台,生产环境务必去掉。
八、小结
一套合格的 Python Web 生产部署 = Gunicorn 跑代码 + systemd 管生命周期 + Nginx 做反代和静态文件。本文的配置全部可以直接套用,关键决策点就三个:worker 数量按 CPU 与 I/O 特性定、timeout 与 Nginx 侧协调一致、max_requests 防内存泄漏。遇到 502/504 先分清是哪一层的问题——Nginx 连不上是 502,应用响应太慢是 504,按排错小节逐步定位即可。




