Gunicorn 与 Nginx:生产环境 Web 服务器配置

📂 所属阶段:第六阶段 — 上线部署(生产篇)
🔗 相关章节:Docker 部署全流程 · 环境变量与安全配置

辛辛苦苦做了三个月的 Flask 项目管理后台终于要上线了!之前一直用 flask run 自测,只要多开几个标签页模拟并发,页面就开始卡顿,偶尔还直接崩出 502。查了一圈才发现,Gunicorn 多进程处理动态请求 + Nginx 反向代理接管静态文件、SSL 与负载均衡入口,是 Python Web 生产部署的“黄金入门组合”。今天我们就来动手搭一套带日志、守护进程、安全配置的完整方案。


1. 为什么要放弃 flask run,选 Gunicorn?

先看看两者的能力边界,你就明白为什么生产环境坚决不能用内置服务器:

功能/特性Flask 内置 flask runGunicorn (Green Unicorn)
适用场景仅本地开发调试工业级 Python WSGI 生产容器
并发模型单线程 + 单进程多 Worker,支持 sync/gevent/uvicorn
高并发处理❌ 超过 10 个请求就卡顿或 503✅ Worker 数按需调整,轻松扛住数百并发
稳定性❌ 崩溃后不会自动重启✅ 可配合 systemd/supervisor 自动重启
部署限制❌ 不能公开暴露端口✅ 安全部署在本地端口,对外只留 Nginx

简单说:flask run 是给开发者调试用的玩具车,Gunicorn 才是真正能上高速的赛车。再搭配一个专业“调度员” Nginx,就能让每一类请求都高效处理。


2. Gunicorn 的安装与基本配置

2.1 环境准备

请确保你的项目在虚拟环境中运行,避免全局依赖污染。激活虚拟环境后直接安装:

# 激活虚拟环境后执行
(venv)$ pip install gunicorn

2.2 启动模式

Gunicorn 支持两种启动方式:命令行传参(适合临时测试)和 配置文件(推荐,更规范易维护)。

命令行快速启动

适合初次尝试,快速验证:

# 假设 app.py 是入口文件,app 是 Flask 实例变量
(venv)$ gunicorn -w 4 -b 127.0.0.1:8000 app:app

# 如果使用了“应用工厂模式”(生产环境推荐),需要用引号包裹工厂调用
(venv)$ gunicorn -w 4 -b 127.0.0.1:8000 "app:create_app('production')"

📌 参数速查
-w 4:启动 4 个 Worker 进程(后面会介绍黄金公式)
-b 127.0.0.1:8000:绑定本地 8000 端口,不要直接暴露在公网,留 Nginx 做入口

配置文件启动(生产必选)

在项目根目录创建 gunicorn.conf.py,所有配置集中管理:

# gunicorn.conf.py
import multiprocessing

# ============== 核心配置 ==============
# 绑定地址/端口:本地 8000,不对外暴露
bind = "127.0.0.1:8000"
# Worker 数量:CPU核心数×2+1(IO 密集型项目可适当增加,CPU 密集型不要超太多)
workers = multiprocessing.cpu_count() * 2 + 1
# Worker 类型:默认 sync(纯 CRUD 接口);可选用 gevent(IO密集型,需单独安装);uvicorn(ASGI异步)
worker_class = "sync"
# 超时时间:Worker 超过 30s 没响应,自动重启避免死锁
timeout = 30
# 优雅退出时间:重启时给 Worker 30s 处理完已有请求
graceful_timeout = 30
# 连接保持时间:配合 Nginx 的 keepalive
keepalive = 65
# 最大请求数:Worker 处理 1000 次请求后自动重启(预防内存泄漏)
max_requests = 1000
# 随机抖动数:避免所有 Worker 同时重启
max_requests_jitter = 50

# ============== 日志配置 ==============
# "-" 表示输出到标准输出/错误,方便配合 systemd 的 journalctl 查看
accesslog = "-"
errorlog = "-"
loglevel = "info"
# 自定义访问日志格式(保留 IP、时间、请求路径、状态码、Referer 等关键信息)
access_log_format = '%(h)s %(l)s %(u)s %(t)s "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s"'

# ============== 钩子函数(可选) ==============
def on_starting(server):
    print("🚀 Gunicorn 正在启动...")

def on_exit(server):
    print("👋 Gunicorn 正在优雅退出...")

使用配置文件启动:

(venv)$ gunicorn -c gunicorn.conf.py "app:create_app('production')"

3. Nginx 反向代理配置

Gunicorn 处理动态请求没问题,但让它同时负责 SSL 加密、静态文件服务、Gzip 压缩就太勉强了——这些专业的事交给 Nginx 来做。

3.1 前置准备

  1. 确保服务器已安装 Nginx
    • Ubuntu/Debian:apt install nginx
    • CentOS:yum install nginx
  2. 准备好域名和 SSL 证书(推荐免费的 Let's Encrypt,通过 certbot 自动申请)
  3. 将静态文件(CSS/JS/图片)、媒体文件(用户上传)集中放到 Nginx 可访问的目录,例如 /var/www/daoman/

3.2 配置文件

创建 /etc/nginx/conf.d/daoman.conf(独立配置,便于维护,切勿直接修改 nginx.conf):

# /etc/nginx/conf.d/daoman.conf

# 1. 定义 Gunicorn 上游服务器(后续若扩展多 Worker 节点,直接添加 server 即可)
upstream daoman_app {
    server 127.0.0.1:8000;
    keepalive 32;   # 保持 32 个长连接,降低握手开销
}

# 2. HTTP 请求强制跳转 HTTPS(生产环境必须!)
server {
    listen 80;
    listen [::]:80;
    server_name daomanpy.com www.daomanpy.com;

    # 将所有 HTTP 流量永久重定向到 HTTPS
    return 301 https://$host$request_uri;
}

# 3. HTTPS 主服务配置
server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name daomanpy.com www.daomanpy.com;

    # ============== SSL 证书配置(certbot 会自动生成这一段) ==============
    ssl_certificate /etc/letsencrypt/live/daomanpy.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/daomanpy.com/privkey.pem;
    ssl_protocols TLSv1.2 TLSv1.3;   # 禁用不安全的 TLSv1.0/1.1
    ssl_prefer_server_ciphers off;
    ssl_session_cache shared:SSL:10m;   # 缓存 SSL 会话,减少握手时间
    ssl_session_timeout 1d;

    # ============== 安全响应头(防 XSS、点击劫持、嗅探攻击) ==============
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-XSS-Protection "1; mode=block" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;

    # ============== Gzip 压缩(降低带宽消耗、提升加载速度) ==============
    gzip on;
    gzip_vary on;
    gzip_min_length 1024;   # 小于 1KB 不压缩
    gzip_types text/plain text/css text/xml application/json
               application/javascript application/xml+rss
               application/atom+xml image/svg+xml;

    # ============== 静态/媒体文件服务(Nginx 直接处理,完全不经过 Gunicorn) ==============
    location /static/ {
        alias /var/www/daoman/static/;
        expires 30d;                      # 缓存 30 天
        add_header Cache-Control "public, immutable";
    }

    location /media/ {
        alias /var/www/daoman/media/;
        expires 7d;                       # 缓存 7 天
        add_header Cache-Control "public";
    }

    # ============== 动态请求代理到 Gunicorn ==============
    location / {
        proxy_pass http://daoman_app;     # 转发到上游服务器
        proxy_http_version 1.1;           # 必须用 1.1 才能支持 keepalive
        proxy_set_header Connection "";   # 清空 Connection 头,复用长连接
        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_connect_timeout 60s;
        proxy_send_timeout 60s;
        proxy_read_timeout 60s;

        # 允许上传的最大文件大小(默认 1MB 可能不够)
        client_max_body_size 20M;
    }
}

💡 让 certbot 自动管理 SSL 证书
首次配置时,先注释掉 listen 443 相关行,让 Nginx 仅处理 HTTP,然后运行 certbot --nginx -d daomanpy.com -d www.daomanpy.com,它会自动申请证书并帮你生成 SSL 配置。

3.3 测试并重载 Nginx

# 1. 检查配置文件语法
sudo nginx -t

# 2. 语法无误则重载(不会中断已有连接)
sudo nginx -s reload

4. Systemd 守护进程管理

生产环境绝不能手动运行 Gunicorn 命令,一旦进程崩溃或服务器重启,服务就没了。用 systemd(Linux 自带的服务管理器)来守护它。

4.1 创建服务文件

创建 /etc/systemd/system/daoman.service

# /etc/systemd/system/daoman.service
[Unit]
Description=Daoman Flask 项目管理后台
# 启动顺序:在网络、PostgreSQL、Redis 之后启动(若不需要数据库/缓存,可删除对应服务)
After=network.target postgresql.service redis.service

[Service]
Type=notify
# 运行用户/组:使用 www-data(Nginx 默认用户),避免权限问题
User=www-data
Group=www-data
# 项目根目录
WorkingDirectory=/opt/daoman
# 虚拟环境路径及生产环境变量
Environment="PATH=/opt/daoman/venv/bin"
Environment="FLASK_ENV=production"
# 其他敏感环境变量建议通过 .env 加载,不要直接写在这里

# 启动命令(必须使用绝对路径)
ExecStart=/opt/daoman/venv/bin/gunicorn \
    -c /opt/daoman/gunicorn.conf.py \
    "app:create_app('production')"

# 重载命令(优雅重启,不中断正在处理的请求)
ExecReload=/bin/kill -s HUP $MAINPID
TimeoutStopSec=5
PrivateTmp=true
# 自动重启策略:异常退出后立即重启
Restart=always
RestartSec=10

[Install]
# 设置开机自启
WantedBy=multi-user.target

4.2 激活与管理服务

# 重新加载 systemd 配置(每次修改 .service 文件后都需要执行)
sudo systemctl daemon-reload

# 启动并设置开机自启
sudo systemctl enable --now daoman

# 查看服务状态
sudo systemctl status daoman

# 重启服务(例如发布新代码后)
sudo systemctl restart daoman

# 优雅重载(仅修改配置,无需重启进程)
sudo systemctl reload daoman

# 实时查看日志(排查问题的神器)
sudo journalctl -u daoman -f

5. 生产部署架构与最佳实践小结

完整架构图

graph LR
    A[用户] -->|HTTPS请求| B[Nginx]
    B -->|静态/媒体文件| C[直接返回]
    B -->|动态请求| D[Gunicorn]
    D -->|读写数据| E[(PostgreSQL)]
    D -->|缓存/会话| F[(Redis)]

3 个关键最佳实践

  1. Worker 数量怎么定?
    纯 CRUD 接口按 CPU 核心数 × 2 + 1 即可;如果是 IO 密集型(爬虫后台、频繁调用第三方 API),可以放大到 核心数 × 4,但不要过量——进程切换的开销也会降低性能。

  2. 安全第一,从入口就守住

    • 强制 HTTPS,不留 HTTP 端口
    • Gunicorn 只绑定 127.0.0.1,绝不对外暴露
    • 静态/媒体文件目录权限设为 www-data:www-data,切忌使用 777
    • 敏感信息(数据库密码、SECRET_KEY 等)通过 .env 或 systemd 注入,绝不写入代码
  3. 无缝发布流程
    每次发布新版本时,只需:

    git pull                    # 拉取最新代码
    sudo systemctl reload daoman   # 优雅重载 Gunicorn,用户无感知

    整个过程不会中断正在处理的请求,真正做到热更新。


🔗 扩展阅读