Web 性能优化实战:HTTP/2 开启、gzip/brotli 压缩与浏览器缓存策略
很多站长买了一台不错的 VPS,带宽也不小,但打开网站就是觉得慢。升级配置之前,先想想流量到底浪费在哪里:同一张页面可能包含几十个 JS/CSS/图片请求,每个请求都要重新走一遍 TCP 握手、TLS 握手、HTTP 请求响应,还要重复传输大量文本字节。本文从传输层入手,讲清楚三个性价比最高的优化动作:开启 HTTP/2、压缩文本响应、设置浏览器缓存,全程给出可复制的配置和排错命令。
一、先测量再优化:找出页面慢的环节
优化的前提是量化。不要凭感觉调参,先用 curl 把一条请求的耗时拆开看。下面这条命令能输出请求的状态码、总耗时、TTFB(首字节时间,反映服务端处理速度)和下载字节数:
curl -o /dev/null -s -w "状态码:%{http_code} 总耗时:%{time_total}s TTFB:%{time_starttransfer}s 下载:%{size_download}B 速度:%{speed_download}B/s\n" https://www.example.com/
常见的三种慢:
- TTFB 很高:瓶颈在服务端(PHP 执行慢、数据库慢),压缩和 HTTP/2 帮不上忙,要查后端。
- TTFB 快但总耗时很长:瓶颈在传输,主要看体积和连接数,本文的方案正好对症。
- 单个请求都很快,但整页很慢:请求数量太多,浏览器串行排队,开启 HTTP/2 多路复用收益最大。
建议优化前先记录每个静态资源的字节数和请求数,优化后再对比,用数据说话。
二、开启 HTTP/2:多路复用消除排队延迟
HTTP/1.1 下浏览器对一个域名通常只能建立 6 个并发连接,资源多了就得排队;HTTP/2 在一个连接上多路复用所有请求,还带头部压缩(HPACK),对几十个静态资源的页面提升非常明显。开启它的前提是:必须使用 HTTPS,且 Nginx 版本不低于 1.9.5、OpenSSL 不低于 1.0.2。
先确认你的 Nginx 是否编译了 http_v2 模块:
nginx -V 2>&1 | grep -o "http_v2_module\|http_ssl_module" | sort -u
# 输出同时包含 http_v2_module 和 http_ssl_module 才说明具备条件
然后修改站点配置文件,监听指令加上 http2:
server {
listen 443 ssl http2;
server_name www.example.com;
ssl_certificate /etc/nginx/ssl/example.com/fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
}
保存后校验并重载:
nginx -t && systemctl reload nginx
排错:怎么确认 HTTP/2 真的生效了?用 curl 直接探测协议版本:
curl -sI --http2 -o /dev/null -w "协商协议:%{http_version}\n" https://www.example.com/
# 输出 "协商协议:2" 表示成功;输出 1.1 则未生效
如果输出仍是 1.1,依次检查:证书是否有效(HTTP/2 强制 TLS);TLS 是否低于 1.2;服务器是否在 443 端口正常监听(ss -lntp | grep 443);是否存在 CDN 或前置反代不支持 ALPN(HTTP/2 需要 TLS ALPN 扩展协商)。
三、gzip 压缩:文本资源立省 60% 流量
HTML、CSS、JS、JSON、SVG 这类文本资源有极高的重复度,gzip 后通常能减少 60%~80% 的体积。Nginx 默认已开 gzip,但很多发行版默认只压缩 html 类型,必须显式补齐 mime 类型。推荐配置:
gzip on;
gzip_vary on;
gzip_comp_level 5;
gzip_min_length 1k;
gzip_buffers 16 8k;
gzip_http_version 1.1;
gzip_types text/plain text/css application/json application/javascript text/javascript application/xml application/xhtml+xml image/svg+xml application/x-javascript text/xml;
# gzip_disable "MSIE [1-6]\.";
要点:
- gzip_comp_level 5 是甜点:1~3 压缩率太低,6~9 会明显消耗 CPU,对 VPS 来说 5 的性价比最高。
- gzip_min_length 1k:小于 1KB 的资源压缩后反而更大,不要压。
- 不要压缩图片和视频:JPEG/PNG/WebP/MP4 本身就是压缩格式,再 gzip 只会白白消耗 CPU。
保存后同样 nginx -t && systemctl reload nginx。用 curl 模拟带 Accept-Encoding 头的浏览器请求来验证:
curl -sI -H "Accept-Encoding: gzip" https://www.example.com/ | grep -i "content-encoding\|content-type"
# 应看到: content-encoding: gzip
排错:为什么没有 content-encoding: gzip?按顺序排查:
- 响应的 content-type 是否在 gzip_types 列表里(API 返回 application/json 但列表漏了最常见);
- 响应体是否小于 gzip_min_length;
- 是否经过 CDN 或反代把编码头吞掉了;
- 后端是否已经压缩过一次——如果 PHP-FPM 层已经输出 Content-Encoding: gzip,Nginx 默认不再二次压缩,此时要改在后端关掉压缩,统一交给 Nginx 做。
四、brotli 压缩:比 gzip 再省 20%
Brotli 是 Google 推出的新一代压缩算法,相同压缩级别下比 gzip 体积再小 15%~25%,主流浏览器早已全量支持。缺点是需要额外安装模块,主流发行版可以通过第三方源或源码编译安装 ngx_brotli:
# 以 Debian/Ubuntu 使用 nginx-extras 或源码编译为例
git clone https://github.com/google/ngx_brotli /tmp/ngx_brotli
cd /tmp/ngx_brotli
git submodule update --init
# 若用源码编译 nginx:
./configure --with-compat --add-dynamic-module=../ngx_brotli
make modules
cp objs/ngx_http_brotli_filter_module.so /etc/nginx/modules/
echo "load_module modules/ngx_http_brotli_filter_module.so;" > /etc/nginx/modules-enabled/50-brotli.conf
加载模块后,在 http 块里开启:
brotli on;
brotli_comp_level 6;
brotli_min_length 1k;
brotli_types text/plain text/css application/json application/javascript text/javascript image/svg+xml application/xml;
Brotli 压缩级别 6 与 gzip 5 的 CPU 开销相当,但体积更小。需要说明:Nginx 的 brotli 和 gzip 可以同时开启,支持 brotli 的浏览器会自动协商使用 brotli,老浏览器回退到 gzip,互不影响。
验证方法与 gzip 相同,把 Accept-Encoding 换成 br:
curl -sI -H "Accept-Encoding: br" https://www.example.com/ | grep -i content-encoding
# 应看到: content-encoding: br
若返回 404 或 502,先确认模块加载成功:nginx -t 是否报 module 相关错误,以及 nginx -V 2>&1 | tr ' ' '\n' | grep brotli 是否能看到模块名。
五、浏览器缓存:让静态资源只传一次
压缩解决"传得少",缓存解决"不用再传"。给不经常变化的静态资源加上长缓存头,用户第二次访问时直接从本地缓存读取,请求根本不会到达服务器。在 Nginx 里针对静态目录配置:
location ~* \.(js|css|png|jpe?g|gif|webp|svg|ico|woff2?)$ {
expires 30d;
add_header Cache-Control "public, max-age=2592000, immutable";
access_log off;
log_not_found off;
try_files $uri =404;
}
几个关键点:
- expires 30d 与 Cache-Control 二选一即可,Nginx 的 expires 会自动生成 Cache-Control,这里显式写出来方便调试。
- immutable 提示浏览器:只要 URL 不变就永远不重新验证,适合配合"文件名带内容 hash"的构建产物(如 app.8f3a2b.js)。
- index.html 和 API 响应千万别加长缓存,否则用户永远看到旧页面;给 HTML 加 no-cache 或极短缓存:
location = /index.html {
add_header Cache-Control "no-cache, must-revalidate";
}
验证缓存头:
curl -sI https://www.example.com/static/app.js | grep -i "cache-control\|etag\|last-modified"
# 应看到: cache-control: public, max-age=2592000, immutable
排错:缓存不生效?检查请求是否落在静态 location 匹配范围内(正则 location 的优先级);检查有没有别的 location 用 add_header 覆盖;还要确认 Nginx 开启 gzip/brotli 后返回的 Vary 头(gzip_vary on)是否正常,否则代理缓存可能把压缩过的内容错误地提供给不支持的客户端。
六、图片优化:页面体积的头号大户
优化完文本资源后,图片往往是剩余流量的大头。VPS 场景下建议做三件事:
- 转 WebP/AVIF 格式:同样画质比 JPEG 小 25%~50%,可以用 cwebp 批量转换:
cwebp -q 80 input.jpg -o output.webp
# 配合 Nginx 的 map 按 Accept 头自动切图:
map $http_accept $webp_suffix {
default "";
"~*webp" ".webp";
}
# location 里: try_files $uri$webp_suffix $uri =404;
- 懒加载:页面首屏外的图片用 loading="lazy" 延迟加载。
- 正确尺寸:不要让浏览器下载 2000px 原图再缩放到 400px 展示,先压到展示尺寸再上传。
七、优化效果验证与整体检查清单
优化结束后,回到第一节的测量命令逐条对比。一个完整的验证流程:
# 1. 检查协议版本
curl -sI --http2 -o /dev/null -w "协议:%{http_version}\n" https://www.example.com/
# 2. 检查 gzip 与 br 是否生效
curl -sI -H "Accept-Encoding: gzip" https://www.example.com/ | grep -i content-encoding
curl -sI -H "Accept-Encoding: br" https://www.example.com/ | grep -i content-encoding
# 3. 对比压缩前后的下载体积
curl -s https://www.example.com/ -o /dev/null -w "无压缩:%{size_download}B\n"
curl -s --compressed https://www.example.com/ -o /dev/null -w "压缩后:%{size_download}B\n"
# 4. 确认缓存头
curl -sI https://www.example.com/static/app.js | grep -i "cache-control"
另外建议用 Chrome DevTools 的 Network 面板配合"Slow 3G 模拟"做一次真实用户视角的加载测试,看瀑布图里还有哪些请求在排队、哪些资源没命中缓存。性能优化是持续过程,每次发布新版本后都跑一遍上面的检查,比出事后再救火省心得多。
八、常见问题速查
| 现象 | 原因 | 解决 |
|---|---|---|
| curl 显示协议 1.1,HTTP/2 未生效 | TLS 版本低于 1.2、证书无效、前置反代不支持 ALPN | 确认 ssl_protocols 含 TLSv1.2/1.3,证书链完整,检查 CDN 配置 |
| gzip 不生效 | content-type 未列入 gzip_types、体积小于 min_length、后端已压缩 | 补齐类型列表,减小阈值,后端关闭压缩统一由 Nginx 处理 |
| brotli 404/502 | 模块未加载、编译时未启用 | nginx -t 检查模块加载行,重编或换含 brotli 的发行版包 |
| 静态资源不缓存 | location 匹配优先级、add_header 被覆盖、Cache-Control 缺失 | 用正则 location 匹配扩展名,检查是否有其他 add_header 覆盖 |
| 压缩后体积反而更大 | 小文件被压缩、图片/视频被 gzip | 调大 gzip_min_length,gzip_types 不要包含图片格式 |
本文覆盖的配置都在 Nginx 层面完成,改动小、可回滚(备份配置文件后 nginx -t 校验再 reload),对任何 LNMP 架构的 VPS 都适用。把测量、压缩、缓存这三板斧练好,大多数静态资源型页面的加载速度都能有明显改善。



