Nginx 静态站的缓存与压缩取舍
静态站的性能配置其实只有两件事:告诉浏览器什么能存、存多久;以及决定服务端要不要压。但这两件事最常见的写法都是错的——不是配得不够,是配得太狠或者配反了。
缓存时长不能一刀切
把所有文件都设成 expires 30d 是最省事也最糟糕的做法:改了 HTML,访客要等一个月才看得到。正确的做法是按「文件名会不会变」来分。
# 1) 带内容指纹的资源:可以永久缓存
location /assets/ {
expires 1y;
add_header Cache-Control "public, max-age=31536000, immutable";
}
# 2) HTML:必须回源校验
location ~* \.html$ {
expires -1;
add_header Cache-Control "no-cache, must-revalidate";
}
# 3) 图片、字体:名称通常不变,给长缓存
location ~* \.(png|jpe?g|gif|webp|svg|woff2?)$ {
expires 180d;
add_header Cache-Control "public, max-age=15552000";
}
关键是 immutable。它告诉浏览器:这个 URL 的内容不会变,连条件请求都别发了。代价是你必须在内容变化时改文件名,比如 style.a1b2c3.css。如果文件名是固定的,就别用 immutable,否则用户会一直看到旧样式。
这类站每次更新都是手动传文件。所以我干脆不做指纹,改成:CSS/JS 走短缓存(max-age=3600),HTML 走 no-cache。多一次条件请求,换掉「改了不生效」的困惑,更划算。
add_header 的层叠陷阱
这是我自己踩过、并且很多配置里都能看到的错误:只要在某个层级写了 add_header,上一层的 add_header 就全部失效,不是叠加。
server {
add_header X-Frame-Options "SAMEORIGIN";
add_header Referrer-Policy "no-referrer";
location /assets/ {
add_header Cache-Control "public, max-age=31536000";
# ↑ 这里一写,上面两条安全头在这个 location 里全部消失
}
}
要么在每个 location 里把需要的头补全,要么把 add_header 全部提到 server 层、location 里只用 expires。后者更好维护。
gzip:只压文本,别贪级别
压缩级别从 5 到 9,文件通常只再小 1–2%,CPU 时间却翻倍。对这种低配机器,5 或 6 是合适的位置。
gzip on;
gzip_vary on;
gzip_comp_level 5;
gzip_min_length 512;
gzip_proxied any;
gzip_types
text/plain
text/css
text/xml
application/javascript
application/json
application/xml
image/svg+xml;
gzip_min_length 512——几十字节的响应压缩后反而更大,还会多出协商开销gzip_vary on——加上Vary: Accept-Encoding,避免 CDN 把压缩版缓存给不支持的客户端- 不要把
image/png、image/jpeg、video/mp4加进gzip_types,它们已经是压缩格式,再压一遍只浪费 CPU
一个实际数字
这个站首页大约 2.6KB 的 HTML,gzip 之后约 1.1KB。压缩率高是因为中文内容重复模式少、文本熵低;但绝对值上省了 1.5KB,对首屏的影响其实很小。
真正省时间的是减少请求数和避免阻塞渲染的外部资源。这个站没有字体文件、没有外部 CSS 框架、没有统计脚本,所以即使不压缩也不会慢。压缩是锦上添花,不是解决方案。
要不要上 Brotli
Debian 官方源里的 Nginx 不带 ngx_brotli,得自己编译或者用带模块的第三方包。Brotli 在文本上比 gzip 再小 15% 左右,但代价是多一个可能出问题的构建链路。
我的判断标准很简单:如果站点总传输量在几百 KB 以内,不值得。等你需要传几 MB 的 JS 包时,再考虑它。
验证
curl -sI -H "Accept-Encoding: gzip" https://example.com/assets/style.css \
| grep -iE "cache-control|content-encoding|vary|expires"
# 期望看到
# cache-control: public, max-age=31536000, immutable
# content-encoding: gzip
# vary: Accept-Encoding
改完配置先 nginx -t 再 systemctl reload nginx,永远不要直接 restart——reload 不会断掉已有连接。