Flask 博客后台和上传接口怎么限流:Nginx 限速、真实 IP 与误伤避坑
很多个人博客把安全只理解成“密码复杂一点”。真正上线后,更常见的问题其实是后台登录被反复试密码、上传接口被脚本猛打、自动发布接口被错误重试放大流量。它们未必会立刻把站点打挂,但足以让小机器出现 502、CPU 飙高、上传卡死,甚至把正常管理员一起误伤。
对 Flask 博客来说,限流最稳的做法不是把所有请求一刀切,而是先把高风险入口拆开:/admin/login、/admin/upload、/api/articles/publish 应该用不同策略。登录接口重点防爆破,上传接口重点防资源耗尽,发布接口重点防脚本重试失控。
一、先分清三类入口的风险
- 登录接口请求体很小,但短时间重复失败通常就是爆破或错误脚本。
- 上传接口单次请求更重,真正危险的是并发和大文件叠加。
- 自动发布接口往往带 Token,风险不只是未授权访问,还包括任务重试把同一批请求放大。
这三类入口如果共用一个全站限流值,结果通常是该拦的不够狠,不该拦的反而先被拦住。
二、第一层先放在 Nginx:按 location 做限流
Nginx 更适合扛在最前面,因为请求还没进 Flask 进程就能被削峰。一个够用的思路是先定义不同 zone:
http {
limit_req_zone $binary_remote_addr zone=admin_login:10m rate=5r/m;
limit_req_zone $binary_remote_addr zone=admin_upload:10m rate=30r/m;
limit_req_zone $binary_remote_addr zone=publish_api:10m rate=10r/m;
server {
location = /admin/login {
limit_req zone=admin_login burst=3 nodelay;
limit_req_status 429;
proxy_pass http://flask_upstream;
}
location = /admin/upload {
limit_req zone=admin_upload burst=5;
limit_req_status 429;
client_max_body_size 8m;
proxy_pass http://flask_upstream;
}
location = /api/articles/publish {
limit_req zone=publish_api burst=2 nodelay;
limit_req_status 429;
proxy_pass http://flask_upstream;
}
}
}
这里的关键不是具体数字,而是“把入口拆开”。登录接口可以更严,因为正常人一分钟登录几次已经很多;上传接口要结合图片尺寸、Pillow 重编码耗时和机器规格来定;发布接口通常只给自动化任务使用,阈值应该明显低于公开页面。
改完后先执行:
sudo nginx -t
sudo systemctl reload nginx
三、第二层要保证拿到真实客户端 IP
如果站点前面还有 CDN、SLB 或反向代理,只按默认 remote_addr 限流,Nginx 和 Flask 看到的可能全是上一跳代理地址。结果要么所有用户共用一个限流桶,要么你的后台自己把自己打成异常流量。
常见做法是让 Nginx 先恢复真实 IP,再让 Flask 只信任明确数量的代理头:
set_real_ip_from 127.0.0.1;
set_real_ip_from 10.0.0.0/8;
real_ip_header X-Forwarded-For;
real_ip_recursive on;
from werkzeug.middleware.proxy_fix import ProxyFix
app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1, x_host=1)
这里最容易踩坑的点,是把 X-Forwarded-For 无条件全信任。更稳的原则是:只信任你自己控制的那一层或那几层代理,不要因为“想拿到真实 IP”就把任何来路的头都当真。
四、Flask 里继续做轻量兜底,不把安全全压给 Nginx
Nginx 负责挡在前面,Flask 负责兜底和记录语义更强的失败行为。对小博客,至少补这三件事:
- 给登录失败做按时间窗口的计数,连续失败就短暂锁定。
- 给上传接口设置
MAX_CONTENT_LENGTH,避免超大请求进到应用层后才发现扛不住。 - 给自动发布接口记录 401、403、429、5xx 的次数,区分是鉴权问题还是限流问题。
一个最基本的大小限制就能挡掉很多无意义流量:
app.config["MAX_CONTENT_LENGTH"] = 8 * 1024 * 1024
如果上传图片还要做 Pillow 打开、转码、缩放,应用层大小限制不能省,因为真正耗 CPU 的部分发生在 Flask 里,不是在 Nginx 配完就结束了。
五、验证时不要只看“能不能访问”,要看 413 和 429 是否分工清楚
上线后至少做两轮验证:
- 用正常管理员流程登录、上传一张合规图片,确认没有误伤。
- 用脚本连续请求登录和上传,确认返回的是预期状态码。
例如:
for i in $(seq 1 10); do
curl -I -X POST https://example.com/admin/login
done
你重点要看两件事:
- 请求过快时是否返回
429 Too Many Requests。 - 文件过大时是否返回
413 Request Entity Too Large。
这两个状态码不要混用。429 说明速率太快,413 说明请求体太大。后面排查日志、调自动化脚本、和外包或同事沟通时,这个区别非常重要。
六、把日志和告警补上,限流才算真正可维护
很多站点第一次配限流时,只盯着配置文件,忘了后续怎么观察。更稳的做法是把 429、413、401、403 分开记到访问日志或应用日志里,至少保留请求路径、客户端 IP、状态码、请求体大小和处理时长。这样你才能判断到底是有人在试密码、编辑器在重复上传同一张图,还是自动化任务在网络抖动后把重试次数打爆。
如果你的站点已经接了简单监控,建议给后台登录和上传接口单独做一条 5 到 15 分钟窗口的异常计数阈值。小博客不一定要上很重的风控系统,但至少要做到“429 突然增加时你能看见,5xx 跟着升高时你能及时回滚”。
七、最常见的四个误区
1. 全站只配一个限流值
首页、后台登录、上传接口和发布 API 的请求特征完全不同。只配一个值,最后通常是谁都不满意。
2. 把 burst 开得很大
burst 不是越大越安全。对上传接口来说,过大的突发值可能让多张图片同时进入 Flask,Pillow 转码瞬间把 CPU 吃满。
3. 忘了真实 IP,导致多人共用一个桶
特别是前面挂了 CDN 或云负载均衡时,这个问题很常见。表现通常是:明明只有你自己在后台操作,却莫名其妙收到 429。
4. 只在开发环境测上传大小
Flask 文档明确提到,开发服务器在超大上传时可能表现成连接被重置,而不是标准 413。真正要看的还是生产 WSGI 和 Nginx 链路里的结果。
总结
对 Flask 博客来说,后台安全不该只靠密码,也不该只靠一条总限流规则。更稳的做法是把登录、上传、自动发布三个入口拆开治理:前面用 Nginx 做 location 级限流和请求体控制,中间保证真实客户端 IP 传递正确,后面用 Flask 补登录失败计数、应用层大小限制和状态码日志。
这样做的价值,不只是“更安全”,而是当你的网站开始有自动化发布、图片上传和长期运维动作后,系统仍然能把异常流量挡在边缘,把正常操作留给后台管理员。