许多教程在介绍 Nginx 启用 Brotli 压缩时,往往只告诉你添加 brotli on 即可。但在实际部署中,远没有这么简单——直接写入配置,大概率会抛出 unknown directive "brotli" 错误。本质上,Brotli 并非 Nginx 的内置功能,必须先编译对应模块,再根据业务场景调整参数,否则所谓的“极致压缩”只能停留在理论层面。
下面先厘清几个关键判断点。
确认 ngx_brotli 模块是否已成功编译进 Nginx
仅仅从 GitHub 克隆了 ngx_brotli 仓库,或者在宝塔面板点击了“添加模块”,并不代表模块已真正生效。验证的核心只有两条:
- 执行
nginx -V 2>&1 | grep with-http_brotli_module,有输出才说明 configure 阶段确实注入了该模块 - 运行
nginx -t后不出现unknown directive "brotli",才代表基础校验通过 - OpenResty 用户必须选用与当前版本匹配的
ngx_brotli分支(例如 OpenResty 1.21.x 对应openresty分支),混用会导致make失败 - 宝塔用户不宜直接 patch 已安装的二进制文件,更稳妥的方案是:卸载 → 编译安装 → 添加自定义模块
在 http 块中启用 brotli 并避免与 gzip 冲突
brotli on 写入 server 或 location 块基本无效,Nginx 会直接忽略。正确做法是将其置于 http 块的顶层,并同时显式关闭 gzip:
brotli on和gzip off必须成对出现,顺序无关紧要,但建议紧挨着写在http块起始位置brotli_static on仅检查同名的.br文件(如app.js.br),既不生成新压缩文件也不降级处理;若需要对动态 JSON 或 HTML 进行实时压缩,应设置brotli_static off,并在对应location中再次声明brotli on- 不要依赖“自动协商”——客户端若未发送
Accept-Encoding: br,服务端绝不会返回Content-Encoding: br
常见的踩坑点在于:许多人以为写上 brotli on 就万事大吉,结果用浏览器查看网络请求,发现仍是 gzip 编码。问题大多出在指令所在层级不对,或未关闭 gzip 导致两者冲突。
按资源类型分层设置 brotli_comp_level 与 brotli_min_length
全局统一设置 brotli_comp_level 11 会严重拖慢 TTFB,尤其在高 QPS 的接口上。经验表明,应根据场景做分级配置:
- 动态内容(API JSON、模板 HTML):
brotli_comp_level 1–4即可,压缩耗时可降低约 60%,而压缩率仍优于gzip 9 - 前端构建产物(
.js/.css):brotli_comp_level 5–6较为均衡,压缩率约 63%,在 iOS/Android 上的解压延迟不超过 8ms - 长期不变的静态资源(SVG、字体、reset.css):使用
brotli_comp_level 8–11配合brotli_static on,前提是构建阶段已生成对应的.br文件 brotli_min_length建议设为1024(1KB),避免小响应(如空 JSON)频繁触发 CPU 压缩
预压缩文件权限与 MIME 类型必须严格匹配
brotli_static on 属于“静默失败”型配置——如果文件存在但权限不正确,或 brotli_types 漏写了 MIME 类型,Nginx 会直接忽略该功能,也不会 fallback 到 gzip。因此务必注意:
- 构建阶段生成
.br文件:Vite 使用vite-plugin-compression,Webpack 使用compression-webpack-plugin,并指定algorithm: 'brotliCompress' brotli_types必须与实际.br文件的 MIME 类型完全一致,例如application/javascript不能误写为text/javascript.br文件的权限需设为644,确保 Nginx worker 进程可读取;否则日志中无任何提示,请求会直接走未压缩路径- 保留
gzip on作为兜底方案——尽管现代浏览器对 Brotli 的支持率已超过 98%,仍有部分监控工具或内网爬虫依赖 gzip
真正的难点不在于写几行配置,而在于将模块编译、MIME 匹配、预压缩生成、权限控制这四个环节串联成一个完整的闭环。遗漏任何一环,所谓的“极致压缩”就只剩下一个 Content-Encoding: br 的虚假表象,看上去美观却无法真正提升性能。

