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(默认同步,可选 async/uvicorn)
高并发处理❌ 超过10个请求就卡顿/503✅ Worker 数量按需调整,可轻松处理数百并发
稳定性❌ 异常中断后不会自启✅ 可配合 systemd/supervisor 自启+重启
部署限制❌ 不能公开暴露端口✅ 安全部署在局域网/本地端口,对外只留 Nginx

2. Gunicorn 的安装与基本配置

2.1 环境准备

确保你的项目在虚拟环境中运行(避免全局依赖污染),然后直接用 pip 安装:

# 激活虚拟环境后执行
(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 进程(核心数×2+1 是黄金公式,后面细说)
  • -b 127.0.0.1:8000:绑定本地 8000 端口(不要直接绑定 0.0.0.0 暴露公网,留 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密集型,需要 pip install gevent);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;

    # 这一行是 certbot 自动生成的?可以保留,或者手动跳转
    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; # 仅允许同域 iframe 嵌入
    add_header X-Content-Type-Options "nosniff" always; # 禁止浏览器自动嗅探 MIME 类型
    add_header X-XSS-Protection "1; mode=block" always; # 开启 XSS 防护
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;

    # ============== Gzip 压缩配置(减少带宽消耗,提高加载速度) ==============
    gzip on;
    gzip_vary on; # 告诉 CDN/代理服务器是否缓存压缩版本
    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; # 传递真实域名给 Flask(Flask 要靠这个生成 redirect 等链接)
        proxy_set_header X-Real-IP $remote_addr; # 传递真实客户端 IP
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # 传递完整 IP 链
        proxy_set_header X-Forwarded-Proto $scheme; # 传递真实协议(http/https)
        proxy_redirect off; # 禁止 Nginx 修改重定向响应的地址

        # 超时配置
        proxy_connect_timeout 60s;
        proxy_send_timeout 60s;
        proxy_read_timeout 60s;

        # 允许上传的最大文件大小(默认是 1MB,不够的话调整)
        client_max_body_size 20M;
    }
}

3.3 测试与重启 Nginx

# 1. 测试配置文件语法是否正确
sudo nginx -t

# 2. 语法没问题,重载 Nginx(不会中断已有连接)
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"
# 可以在这里加其他项目环境变量(比如数据库密码、SECRET_KEY),但更推荐用 .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
# 重启间隔:10秒
RestartSec=10

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

4.2 激活与管理服务

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

# 2. 启动服务并设置开机自启
sudo systemctl enable --now daoman

# 3. 查看服务状态
sudo systemctl status daoman

# 4. 重启服务(发布新代码后用)
sudo systemctl restart daoman

# 5. 优雅重载服务(如果只是修改配置,没改代码)
sudo systemctl reload daoman

# 6. 查看实时日志(super好用!排查问题必备)
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),可以加到「CPU核心数×4」,但不要超太多(会增加进程切换开销)。
  2. 安全第一
    • 强制 HTTPS(不要留 HTTP 端口)
    • Gunicorn 只绑定本地 127.0.0.1
    • 静态/媒体文件目录权限设为 www-data:www-data,不要给 777
    • 环境变量不要写在代码里,用 .env 或 systemd 配置
  3. 发布流程优化:用 git pull 拉新代码 → systemctl reload daoman 优雅重载(发布期间不中断用户请求)。

🔗 扩展阅读