2026-10-24
静态站提速:Gzip 与缓存头的实测记录
站点能打开之后,很容易停在「反正它是静态页,应该挺快」的错觉里。这次把响应头抓下来看,才发现 /etc/nginx/nginx.conf 里关于压缩只剩孤零零一行 gzip on;,下面那一整块参数全部是注释状态,缓存头则一条都没有。这篇记录从测量到改配置的完整过程,以及中途踩到的一个小坑。
一、先量一遍现状,别猜
判断有没有压缩,看响应头最直接,不用装任何工具:
curl -s -o /dev/null -D - -H "Accept-Encoding: gzip" https://eeuse.com/
返回里只有一行 content-encoding: gzip,vary 没有,cache-control 也没有。这说明 HTML 确实被压了,但除此之外什么都没有。
二、gzip on 只压了 HTML
Ubuntu 自带的 nginx.conf 里,压缩那一段长这样:
gzip on;
# gzip_vary on;
# gzip_proxied any;
# gzip_comp_level 6;
# gzip_buffers 16 8k;
# gzip_http_version 1.1;
# gzip_types text/plain text/css application/json ...;
只有一个开关打开,其余全是注释。默认行为是:只压缩 text/html,压缩级别 1,最小长度 20 字节。也就是说 CSS、JS、JSON、SVG 这些都没走压缩,HTML 用的还是最低档。对一个纯静态站来说,这等于白留了大部分收益。
三、把参数补齐
改动只在这一个文件里,替换掉原来那一行 gzip on; 所在的段落:
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_min_length 1024;
gzip_buffers 16 8k;
gzip_http_version 1.1;
gzip_types text/plain text/css application/json application/javascript
text/xml application/xml application/xml+rss text/javascript
image/svg+xml application/rss+xml;
gzip_vary on:告诉 CDN 和代理「同一个 URL 会因编码不同而有不同响应」,漏掉它容易出现代理把压缩版发给不支持的客户端gzip_comp_level 6:默认的 1 几乎不省空间,6 是体积和 CPU 的常见平衡点,静态小站完全承受得住gzip_min_length 1024:太小的文件压完可能更大,还多一次 CPU,挡掉gzip_types:默认只有 HTML,这一行才是真正的收益来源
四、压缩效果:实测数字
改完重载,同一份文件分别按「接受 gzip」和「不接受」各取一次长度:
GZ=$(curl -s -H "Accept-Encoding: gzip" https://eeuse.com/ | wc -c)
PL=$(curl -s -H "Accept-Encoding: identity" https://eeuse.com/ | wc -c)
- 首页:12,072 → 4,101 字节,省掉约 66%
- 文章页:14,542 → 6,119 字节,省掉约 58%
两页都是样式内联的单文件 HTML,压缩率差异主要来自重复的 CSS 声明。注意别被自己骗了:curl 不加 --compressed 也不会自动解压,量之前先把两种编码区分清楚,否则量到的就是压缩后的体积,白高兴一场。
.css 和 .js 文件,样式全部内联在 HTML 的 <style> 里。所以下面谈缓存时,真正生效的对象是 HTML 本身,而不是想象中的那堆静态资源。五、缓存头要按资源类型分开
缓存时间不是越长越好。HTML 是入口,发布新文章后必须尽快生效;图片、字体这类内容改一次就不再变,才适合长缓存。所以在 443 的 server 块里按类型分了两个 location:
location ~* \.(css|js|svg|png|jpg|jpeg|webp|ico|woff2?)$ {
add_header Cache-Control "public, max-age=2592000, immutable";
access_log off;
}
location ~* \.html$ {
add_header Cache-Control "public, max-age=3600";
}
HTML 只给 1 小时:比「不缓存」省掉重复请求,又不至于发了文章别人两天看不到新版。真正的静态资源给 30 天并加 immutable,浏览器在有效期内连协商请求都不发。
六、踩到的坑:两条 cache-control
第一次写的时候,为了兼容只认 Expires 的老客户端,我把两种写法都留上了:
expires 1h;
add_header Cache-Control "public, max-age=3600";
结果 curl -D - 打出来是这样的:
cache-control: max-age=3600
cache-control: public, max-age=3600
两条同时出现。expires 指令本身就会自动生成一条 Cache-Control: max-age,再手写一条就重复了。浏览器大多能容忍,但这是个没必要的歧义,而且日志和调试时看着很乱。最终做法是只留 add_header 一种写法,现在响应里干净地只剩一行。
七、线上改配置的正确姿势
改配置最怕的是改错了又 reload,站点直接 502。所以我把它做成一个带自动回滚的脚本,逻辑就四步:
- 先备份,文件名带时间戳,
nginx.conf.bak.20261024110657这种,回滚时不用回忆 - 改完先
nginx -t,只做语法和文件存在性检查,不会影响正在跑的进程 - 检查通过才
systemctl reload nginx,不重启、不断连接 - 检查不通过就把备份文件拷回去,再跑一次
nginx -t确认恢复干净
if nginx -t 2>&1; then
systemctl reload nginx
else
cp -a "/etc/nginx/nginx.conf.bak.$TS" /etc/nginx/nginx.conf
nginx -t 2>&1 || true
exit 1
fi
改完立刻验证两件事:目标页面还是 200,随手指一个不存在的路径还应该返回 404。少验一个,就可能把 try_files 改坏而不自知。
八、还能再往前一步
- Brotli:比 gzip 再省 15% 左右,但 Ubuntu 官方 Nginx 包没带这个模块,要自己编译或换源,属于收益明显但动手成本高的一步
- HTTP/2:这个站点早就开了,压缩和缓存是叠加收益,不冲突
- 看真实节省:日志里同时记
$body_bytes_sent和$request,跑几天就能算出实际少发了多少流量
小结
gzip on;只压 HTML,级别还是 1,等于只开了一小半gzip_vary和gzip_types是最该先补的两项- 压缩前先量:首页 12,072 → 4,101,文章页 14,542 → 6,119
- 缓存要按类型分:HTML 1 小时,静态资源 30 天加 immutable
expires和add_header Cache-Control别同时写,会出两条头- 改线上配置固定四步:备份、
nginx -t、reload、失败回滚
本篇阅读 — 次