Gzip压缩是提升Web页面加载速度最立竿见影的手段之一,理论上可以将HTML文本压缩到原体积的三分之一甚至更小。但不少开发者在Nginx中配置了gzip on之后,用浏览器开发者工具查看响应,发现传输体积并没有变化,Transfer Size依然和原文件差不多大。这个问题在PHP动态页面的场景下尤其常见,原因往往不是压缩没开启,而是压缩的触发条件没有被满足。本文从Nginx的gzip模块入手,把常见的失效原因逐一拆解。

一、先确认压缩到底有没有生效:用curl做基准验证
排查的第一步不是改配置,而是验证现状。浏览器开发者工具中的Size列有时具有迷惑性,特别是开了缓存或者Service Worker之后,显示的数据并不能真实反映网络传输情况。用curl直接请求是最可靠的方式,关键是要显式带上Accept-Encoding请求头,因为Nginx的gzip模块只在浏览器声明自己支持压缩时才会工作:
curl -H "Accept-Encoding: gzip" -I https://www.ipipp.com/index.php # 观察返回头中的 Content-Encoding 字段 # 如果没有 Content-Encoding: gzip,说明压缩确实没生效
如果响应头里出现了Content-Encoding: gzip,说明Nginx端压缩是正常的,问题可能出浏览器端或者中间的CDN、代理层,这种情况另行分析。如果确实没有这个头,那压缩在Nginx这一层就没触发,继续往下排查。注意一个细节:-I发送的是HEAD请求,某些框架对HEAD请求的处理不同,稳妥起见可以改用curl -s -H "Accept-Encoding: gzip" -o /dev/null -w "%{size_download}"来观察实际下载体积。
还有一个容易忽略的场景:请求根本没经过Nginx的gzip处理。比如本地开发时用的是PHP内置服务器(php -S),或者请求被直接转发到了PHP-FPM的其他端口,这时无论Nginx配置怎么写都不会生效。先确认请求链路,再谈压缩配置。
二、gzip_types:Content-Type不匹配是最常见的失效原因
Nginx的gzip模块默认只压缩text/html这一种类型,也就是所谓的gzip_types默认值为text/html。这一点非常关键,很多教程会告诉你加上gzip on就万事大吉,但实际上如果你的PHP接口返回的是application/json,或者页面引用的JS、CSS文件不在压缩列表里,Nginx会原样转发,不做任何压缩。
正确的做法是显式声明需要压缩的MIME类型,并且注意类型要和上游返回的Content-Type完全匹配。比如PHP返回JSON时常用header('Content-Type: application/json'),如果配置里只写了text/plain,就不会命中压缩规则。常见的配置如下:
gzip on;
gzip_types text/plain text/css text/javascript application/javascript
application/json application/xml text/xml image/svg+xml;
# 注意 text/html 是默认包含的,无需重复写,写了也没问题
gzip_vary on; # 给代理服务器加上 Vary: Accept-Encoding 头这里有一个高频踩坑点:gzip_vary。如果页面经过CDN或反向代理,缺少Vary: Accept-Encoding响应头会导致代理把压缩版本的内容缓存后返回给不支持解压的客户端,或者反过来缓存了未压缩版本,造成压缩时有时无的诡异现象。开启gzip_vary on基本是必选项。
另外要注意gzip_min_length的设置。默认值是20字节,通常建议设置为1K左右。但如果你把这个值设得过大,比如100K,那么中小体积的页面和接口响应都会被跳过压缩。这个值是依据Content-Length来判断的,对chunked传输的动态响应,Nginx会自行处理,但如果PHP端输出了明确的Content-Length且小于阈值,压缩就不会发生。
三、压缩级别与代理配置:容易被误解的两个参数
gzip_comp_level控制压缩级别,取值1到9。很多人以为级别越高压缩效果一定越好,实际上并非如此。级别越高CPU消耗越大,压缩耗时越长,而文本类内容在级别4到6之间时,压缩率已经接近饱和,继续提高级别收益微乎其微,反而可能拖慢响应速度,尤其在高并发的PHP动态页面上,CPU本来就是瓶颈。生产环境推荐设置为5或者6:
gzip_comp_level 5; gzip_buffers 16 8k; gzip_disable "MSIE [1-6]\.(?!.*SV1)"; # 排除老旧浏览器
第二个容易被误解的是gzip_proxied。当Nginx作为反向代理、上游是PHP-FPM或者另一台服务器时,对代理过来的响应默认不压缩。也就是说,即使你的PHP应用通过FastCGI返回了HTML,只要Nginx判定这是代理响应(HTTP/1.0协议的代理响应默认被视为uncompressed),就有可能跳过压缩。加上如下配置可以解决:
gzip_proxied any; # 对所有代理响应都允许压缩
除了配置层面,还要检查是否有东西在Nginx之前或之后干扰了压缩。典型的干扰源包括:CDN回源时没带Accept-Encoding头;负载均衡器做了响应改写;PHP代码里手动调用了ob_gzhandler或ini_set('zlib.output_compression', 'On'),导致响应已经被压缩过一次,Nginx看到的内容是二进制,即使类型匹配也不会再压。PHP端的输出压缩和Nginx的gzip只能保留一个,推荐统一交给Nginx处理,性能更好也更可控。
四、一份可直接使用的完整配置与排查清单
综合以上分析,下面是一份在多个生产环境验证过的Nginx gzip配置模板,放在http块或者server块中均可:
gzip on;
gzip_min_length 1k;
gzip_comp_level 5;
gzip_types text/plain text/css text/javascript application/javascript
application/json application/xml text/xml image/svg+xml;
gzip_vary on;
gzip_proxied any;
gzip_buffers 16 8k;
gzip_disable "MSIE [1-6]\.";修改配置后别忘了nginx -t检查语法再nginx -s reload,并且reload之后务必清空浏览器缓存或者用curl重新验证,静态资源还可能被浏览器强缓存,让你误以为配置没生效。按照以下顺序排查,基本能覆盖所有失效场景:
- 用curl带
Accept-Encoding: gzip验证响应头是否包含Content-Encoding: gzip; - 检查请求是否真的经过了这台Nginx,排除CDN、本地开发服务器等链路干扰;
- 核对PHP返回的Content-Type是否在
gzip_types列表中; - 检查
gzip_min_length是否设置过大导致小响应被跳过; - 反向代理场景确认
gzip_proxied已配置; - 确认PHP端没有开启
zlib.output_compression或ob_gzhandler造成双重压缩冲突; - 检查配置文件加载顺序,避免gzip相关的include被后加载的配置覆盖。
最后补充一点,图片格式如PNG、JPG、WebP本身已经是压缩格式,gzip对它们几乎无效还会白白消耗CPU,所以不要把它们加进gzip_types。对于JSON接口、HTML页面和静态的CSS、JS文件,gzip依然是性价比最高的优化手段,把配置理顺之后,配合HTTP缓存策略,页面加载速度的提升会非常直观。