为什么 Flask 博客上线后最容易翻车

本地能跑,不代表适合生产。很多个人站点直接用 flask run 顶在线上,SSH 一断或机器一重启,服务就没了。还有一种更隐蔽:首页能开,但后台登录跳转成 http、日志里拿不到真实 IP、站点地图链接不对,根因往往是反向代理头没配好。

对个人博客来说,先把最小可用链路搭稳最重要:Gunicorn 跑 Flask,Nginx 接公网请求和静态文件,systemd 负责自启。这样后面再加 HTTPS、SEO 和自动发布,维护成本才低。

一套够用的部署结构

建议目录类似这样:

/var/www/flask-blog/
  app.py
  config.py
  requirements.txt
  database.db
  static/
  templates/
  venv/

Gunicorn 只监听本机,例如 127.0.0.1:8000,公网流量全部先走 Nginx,再转发给 Gunicorn。这样静态文件可以由 Nginx 直接返回,应用本身也不用暴露公网端口。

第一步:先在虚拟环境里把 Gunicorn 跑起来

cd /var/www/flask-blog
python3 -m venv venv
source venv/bin/activate
pip install -U pip
pip install -r requirements.txt
pip install gunicorn

这个项目入口在 app.py,可以直接试跑:

gunicorn --bind 127.0.0.1:8000 --workers 2 --threads 4 app:app

个人博客通常不需要太多 worker。1 核 2G 小机器先用 2 个 worker 起步更稳,开太大反而更容易吃掉内存。这里如果都启动不了,优先查虚拟环境、依赖和环境变量。

第二步:把代理头和 ProxyFix 配对

Flask 在代理后面运行时,必须正确识别真实协议、主机名和来源地址。这个项目已经在 app.py 里接了 ProxyFixconfig.py 里也有这些开关:

BEHIND_PROXY = True
PROXY_FIX_X_FOR = 1
PROXY_FIX_X_PROTO = 1
PROXY_FIX_X_HOST = 1

Nginx 这一侧至少要补齐:

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;

最容易踩坑的是没传 X-Forwarded-Proto。Nginx 明明已经做了 HTTPS 终止,但 Flask 仍把请求当成 http,后台回跳、绝对链接、canonical URL、sitemap 都可能跟着错。另一个坑是 ProxyFix 的数字别乱写。只有一层可信 Nginx 就填 1;如果前面还有 CDN 或负载均衡,要按真实层数算。

第三步:用 systemd 托管 Gunicorn

生产环境不要靠手工开进程,应该交给 systemd:

[Unit]
Description=Flask Blog Gunicorn Service
After=network.target

[Service]
User=www-data
Group=www-data
WorkingDirectory=/var/www/flask-blog
Environment="FLASK_ENV=production"
Environment="BEHIND_PROXY=true"
Environment="SESSION_COOKIE_SECURE=true"
ExecStart=/var/www/flask-blog/venv/bin/gunicorn --workers 2 --threads 4 --bind 127.0.0.1:8000 app:app
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target

保存为 /etc/systemd/system/flask-blog.service 后执行:

sudo systemctl daemon-reload
sudo systemctl enable flask-blog
sudo systemctl start flask-blog
sudo systemctl status flask-blog

如果服务没起来,直接看:

sudo journalctl -u flask-blog -n 100 --no-pager

常见问题就是工作目录写错、虚拟环境路径不对、数据库权限不足。

第四步:让 Nginx 处理公网入口和静态文件

server {
    listen 80;
    server_name example.com www.example.com;

    location /static/ {
        alias /var/www/flask-blog/static/;
        expires 7d;
        add_header Cache-Control "public, max-age=604800";
    }

    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;
        proxy_read_timeout 60;
    }
}

这里重点看两件事。第一,alias 路径必须是真实绝对路径,且 Nginx 用户能读到;很多“样式丢失”其实只是静态目录权限错了。第二,改完配置一定先执行:

sudo nginx -t
sudo systemctl reload nginx

第五步:再补 HTTPS 和数据库备份

站点能稳定响应后,下一步就该启 HTTPS,并把 SESSION_COOKIE_SECURE=true 固定下来。数据库方面,小流量博客继续用 SQLite 完全没问题,但必须定时备份。这个仓库里已经有 scripts/backup_sqlite.py,可以配合 cron 跑;等后台编辑、评论和自动发布量明显上来,再考虑迁到 MySQL。

常见避坑点

1. 用 flask run 顶生产

开发服务器不是给生产用的。

2. 反代能开首页就以为配置对了

真正容易错的是登录、跳转、绝对链接和 sitemap,这些都依赖正确的代理头。

3. 服务用户和文件权限混乱

Gunicorn 如果用 www-data 跑,那么项目目录、SQLite 文件、上传目录也要给到对应权限,否则发文或上传时很容易报 500。

总结

个人 Flask 博客的生产部署,核心不是“技术栈多新”,而是底座是否稳定。Gunicorn 本机监听、systemd 托管进程、Nginx 正确反代、HTTPS 和代理头配齐,这四件事做对了,站点可维护性就会明显提升。底层稳定以后,再做 SEO、自动化发布和内容运营,才不会反复被基础设施问题拖住。