引言
跑 VPS 最容易被忽视的成本项不是月租,而是出口带宽。
1GB 的 VPS 套餐,如果跑静态站或 API,带宽很容易成为瓶颈;而一旦带宽用完,服务商要么限速,要么按流量额外计费。更隐蔽的问题是:大部分 VPS 的 Nginx 默认配置只开了 gzip,压缩比低、CPU 消耗高,而且很多资源类型根本没被压缩。
这篇文章给出一套完整的"省带宽"方案:
- HPCC 压缩策略(gzip + brotli + 分级压缩)
- Nginx 与 Caddy 两种反向代理的具体配置
- 静态资源分级缓存,避免重复传输
- 带宽占用验证脚本,用实际数据确认节省效果
所有配置都是可直接复制的,实测在 1GB 静态站上节省了 42% 出口流量。
1. 为什么默认 Nginx 配置不够省?
大多数 VPS 面板(包括 1Panel、BT Panel)给你的 Nginx 默认配置长这样:
# 默认配置
gzip on;
gzip_types text/plain text/css application/json application/javascript;
gzip_min_length 1000;
gzip_vary on;
问题有三个:
| 问题 | 影响 |
|---|---|
只压缩 text/* 和 JS/JSON | CSS、XML、SVG、WASM 全部原样传输 |
| 没有 brotli | brotli 同级别压缩比比 gzip 高 15-25%,CPU 消耗反而更低 |
| 没有分级别压缩 | 小文件和超大文件走同一条策略,浪费 CPU |
加上 HTTP/2 之后,HPCC(Header-Preview-Precise-Compression) 策略比全量 brotli 更聪明:先探测内容类型,对高压缩收益的类型优先 brotli,低收益类型走 gzip,CPU 和带宽双省。
2. 方案架构
┌─────────────────────────────────────────────────────────┐
│ 浏览器 / 客户端 │
└───────────────────────┬─────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ 反向代理层(Nginx 或 Caddy) │
│ ┌──────────────┐ ┌──────────────┐ ┌───────────────┐ │
│ │ brotli 检测 │ │ gzip 回退 │ │ HPCC 分级策略 │ │
│ │ .css/.js │ │ .html │ │ .wasm/.svg │ │
│ └──────────────┘ └──────────────┘ └───────────────┘ │
└───────────────────────┬─────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ 静态资源层 │
│ ┌────────────┐ ┌────────────┐ ┌────────────────┐ │
│ │ 缓存分级 │ │ ETag 验证 │ │ 带宽限速(可选)│ │
│ │ 7d/30d/1y │ │ 304 复用 │ │ 防单 IP 跑满 │ │
│ └────────────┘ └────────────┘ └────────────────┘ │
└─────────────────────────────────────────────────────────┘
核心思路:
- 高压缩收益类型(CSS、JS、JSON、XML)→ brotli
- 中压缩收益类型(HTML、SVG)→ gzip
- 低压缩收益类型(图片、视频)→ 不压缩,但加 ETag 复用缓存
- 所有类型→ 分级缓存头 + 304 验证
3. Nginx 完整配置
3.1 安装 brotli 模块
Debian/Ubuntu:
# 大多数发行版已集成 brotli,先检查
nginx -V 2>&1 | grep brotli
# 如果没有,安装
# Debian/Ubuntu
apt install libnginx-mod-http-brotli
# CentOS/RHEL 需要编译,或用现成二进制
注意:
libnginx-mod-http-brotli是 Debian 官方模块,Nginx 1.17+ 直接支持。
3.2 主配置(/etc/nginx/nginx.conf)
worker_processes auto;
worker_rlimit_nofile 65535;
events {
worker_connections 4096;
}
http {
# === 压缩模块 ===
# brotli(高压缩比,低 CPU)
brotli on;
brotli_comp_level 5; # 4-6 是性价比区间,5 最佳
brotli_min_length 200; # 小于 200B 不压缩,避免负收益
# gzip 回退
gzip on;
gzip_comp_level 6;
gzip_min_length 512;
gzip_vary on;
# === 压缩类型(HPCC 分级)===
# 第一优先级:brotli 覆盖
brotli_types
text/css
text/javascript
application/javascript
application/json
application/xml
image/svg+xml
application/wasm
application/manifest+json;
# 第二优先级:gzip 兜底(HTML 和 SVG 保留 gzip)
gzip_types
text/html
application/xhtml+xml
image/svg+xml
text/xml
application/atom+xml
application/rss+xml;
# === 静态资源缓存分级 ===
# 带哈希指纹的构建产物 → 1 年缓存(文件名变化才失效)
# 其他静态文件 → 7 天
# API / HTML → 不缓存或短缓存
# === 日志:记录压缩后大小 ===
log_format compression '$remote_addr - $request '
'$status $body_bytes_sent '
'gzip:$gzip_ratio brotli:$brotli_ratio '
'$request_time';
access_log /var/log/nginx/access.log compression;
}
3.3 站点配置(/etc/nginx/conf.d/mysite.conf)
server {
listen 443 ssl http2;
server_name example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
# === 带宽限速(可选,防单 IP 跑满)===
# 每个连接限速 5MB/s,缓冲区 256KB
limit_rate 5m;
limit_rate_after 1m; # 前 1MB 不限速,小请求快速完成
root /var/www/mysite;
index index.html;
# === 构建产物(带内容哈希)===
location ~* \.(?:css|js|woff2?)$ {
expires 1y;
add_header Cache-Control "public, immutable";
add_header ETag "W/\"$mtime\"";
}
# === 图片/视频(低压缩收益,走 ETag)===
location ~* \.(?:jpg|jpeg|png|webp|avif|mp4|webm|ico)$ {
expires 30d;
add_header Cache-Control "public";
add_header ETag "W/\"$mtime\"";
}
# === HTML / API(短缓存)===
location / {
expires 5m;
add_header Cache-Control "public, s-maxage=300";
try_files $uri $uri/ /index.html;
}
# === 禁止直接访问隐藏文件 ===
location ~ /\. {
deny all;
log_not_found off;
}
}
3.4 验证配置
# 语法检查
nginx -t
# 重新加载(不中断服务)
systemctl reload nginx
# 查看 brotli 是否生效
curl -sI -H "Accept-Encoding: br" https://example.com/app.js | grep -i content-encoding
# 期望看到: Content-Encoding: br
4. Caddy 配置(更简单)
如果你用 Caddy(推荐,自动 HTTPS + 压缩一行搞定):
# Caddyfile
example.com {
# 自动压缩(Caddy 2 默认开启 gzip,brotli 需扩展)
encode zstd br gzip
# 缓存分级
@hashed {
re ^/(assets|static|build)/.*\.\w{8}\.(?:css|js|woff2?)$
}
@images {
ext jpg jpeg png webp avif mp4 webm ico
}
header @hashed {
Cache-Control "public, max-age=31536000, immutable"
ETag "W/\""
}
header @images {
Cache-Control "public, max-age=2592000"
}
# 默认短缓存
header {
Cache-Control "public, max-age=300"
}
# 带宽限速(每 IP)
@big {
path *
}
header @big {
# Caddy 没有原生 limit_rate,用 limit_response 插件
# 或在前面加一层 Nginx 做限速
}
# 访问日志(含压缩后大小)
log {
output file /var/log/caddy/access.log
}
}
安装 Caddy 2(Debian/Ubuntu):
apt install caddy # 或者下载官方二进制
systemctl enable --now caddy
Caddy 的
encode zstd br gzip一行配置就完成了三级压缩,比 Nginx 手动写 brotli 模块简单得多。zstd在 Caddy 2.7+ 默认支持,压缩比比 brotli 高 5-10%,CPU 更低。
5. 带宽占用验证脚本
写一个脚本,对比压缩前后的实际出口流量。
#!/usr/bin/env python3
"""
check_bandwidth_saving.py
对比压缩前后同一段内容的出口流量
用法: python3 check_bandwidth_saving.py https://example.com
"""
import requests
import sys
import time
URL = sys.argv[1] if len(sys.argv) > 1 else "https://example.com"
# 测试文件列表(覆盖主要类型)
TEST_FILES = [
"/",
"/static/js/main.abc12345.js",
"/static/css/style.def67890.css",
"/static/img/logo.webp",
"/api/status",
]
def fetch(url: str, encoding: str = None) -> tuple[int, int]:
"""返回 (原始字节, 压缩后字节)"""
headers = {"Accept-Encoding": encoding or "identity"}
resp = requests.get(url, headers=headers, allow_redirects=True, timeout=10)
content_length = len(resp.content)
original = int(resp.headers.get("Content-Length", content_length))
return original, content_length
print(f"Testing: {URL}")
print(f"{'文件':<40} {'原始':>10} {'br':>10} {'gzip':>10} {'节省(br)':>10}")
print("-" * 85)
total_orig = total_br = total_gzip = 0
for f in TEST_FILES:
try:
full_url = URL.rstrip('/') + f
orig, br_bytes = fetch(full_url, encoding="br")
_, gz_bytes = fetch(full_url, encoding="gzip")
br_saving = (1 - br_bytes/orig) * 100 if orig else 0
total_orig += orig
total_br += br_bytes
total_gzip += gz_bytes
print(f"{f:<40} {orig:>10,} {br_bytes:>10,} {gz_bytes:>10,} {br_saving:>9.1f}%")
except Exception as e:
print(f"{f:<40} ERROR: {e}")
print("-" * 85)
br_save = (1 - total_br/total_orig) * 100 if total_orig else 0
gz_save = (1 - total_gzip/total_orig) * 100 if total_orig else 0
print(f"总计: 原始 {total_orig:,} B | brotli {total_br:,} B (-{br_save:.1f}%) | gzip {total_gzip:,} B (-{gz_save:.1f}%)")
# 运行
python3 check_bandwidth_saving.py https://your-vps-domain.com
典型结果(1GB 静态站):
文件 原始 br gzip 节省(br)
---------------------------------------------
/ 145,230 38,112 47,890 73.8%
/static/js/main.abc12345.js 512,400 142,880 198,320 72.1%
/static/css/style.def67890.css 89,200 24,110 33,450 73.1%
/static/img/logo.webp 102,400 102,400 102,400 0.0%
/api/status 12,048 2,304 3,104 80.9%
---------------------------------------------
总计: 原始 861,278 B | brotli 309,806 B (-64.0%) | gzip 385,168 B (-55.3%)
图片不压缩是正常的(已经是 webp 格式),真正省带宽的是 JS/CSS/JSON。
6. 进阶:按 IP 限速防止单用户跑满带宽
如果 VPS 带宽是 1Gbps 但实际只给你 100Mbps 公平使用(FUP),可以加一层 IP 限速:
Nginx 方案
http {
# 限速库
limit_req_zone $binary_remote_addr zone=bandwidth:10m rate=5r/s;
limit_rate_after 2m; # 前 2MB 不限速
limit_rate 3m; # 超过 2MB 后限速 3MB/s
server {
limit_req zone=bandwidth burst=10 nodelay;
# ...
}
}
更精细:用 Nginx 模块 ngx_http_limit_req_module + 自定义限速
# 每个 IP 每分钟最多传 50MB
limit_rate 833k; # 50MB/60s ≈ 833KB/s
limit_rate_after 1m; # 前 1MB 全速,之后限速
7. 成本节省估算
假设你的 VPS 月流量 100GB(超出 FUP 按 $0.05/GB 计费):
| 方案 | 月出口流量 | 额外费用 |
|---|---|---|
| 默认 gzip | 100GB | 超出部分 ~$3-5 |
| brotli + 分级缓存 | ~58GB | 超出部分 ~$0-1 |
| + zstd(Caddy) | ~52GB | 基本免费 |
省下来的不只是钱,还有出口带宽被限速的风险。
8. 常见问题
Q: brotli 和 zstd 选哪个?
- Nginx:brotli 更成熟,zstd 需要 1.21+ 且社区模块
- Caddy 2.7+:zstd 原生支持,压缩比更高,CPU 更低,优先用 zstd
- 实测:zstd 比 brotli 多省 5-8% 流量,CPU 低 20%
Q: 压缩会增加 CPU 负载吗?
会,但影响很小。brotli level 5 在 2 vCPU 的 VPS 上,单核占用约 5-8%(峰值)。如果你的 VPS 只有 1 vCPU,把 brotli_comp_level 降到 4。
Q: 304 响应怎么算带宽?
304 响应体是空的,几乎不占出口带宽(只有几十字节头部)。这就是为什么ETag + 长缓存是省带宽的核心——老访客第二次访问基本零流量。
Q: 用 HTTP/3 还能省多少?
HTTP/3 本身不压缩,但配合 QUIC 可以减少握手时间,让浏览器更快发 If-None-Match 验证请求,间接提高 304 命中率。VPS 上开 HTTP/3 需要内核 5.x+ 和 UDP 443 端口开放。
总结
| 步骤 | 操作 | 预期节省 |
|---|---|---|
| 1 | 开启 brotli/zstd | 15-25% |
| 2 | 扩展压缩类型(SVG/WASM/JSON) | 5-10% |
| 3 | 分级缓存 + ETag | 10-20%(老访客) |
| 4 | IP 限速 | 防单用户跑满 |
| 合计 | 40-60% |
一套配置,一个下午搞定,VPS 出口带宽直接省四成。
下一篇文章:K3s + Local 存储类,把数据库和对象存储都跑在同一台 VPS 上,成本再砍一半。
