上個月我們幫一位跑 SaaS 後台的客戶排障:2 核 4G 的 VPS,CPU 常年 20%,但每到下午三點網站就卡成投影片。雲端廠商監控裡出站頻寬曲線頂格,套餐 100 Mbps 被吃滿整整四十分鐘。他第一反應是「該升配了」——我們打開 Nginx 存取日誌,發現 70% 的流量是未壓縮的 JSON API 回應和帶 ?v= 隨機參數的靜態 JS,同一批資源被重複全量下發。
調完 Gzip、靜態快取標頭和 upstream keepalive 之後,峰值頻寬降到 35 Mbps,頁面載入時間從 8 秒縮到 1.2 秒,一分錢沒多花。這類故事在中小 VPS 上太常見了:瓶頸在出口頻寬,而不在算力。Nginx 作為反向代理站在使用者與後端之間,天然是「省頻寬」的最佳槓桿——前提是你要知道該擰哪幾顆螺絲。
本文不講 Nginx 安裝入門,而是把我們生產環境裡驗證過的一套頻寬導向調校清單整理出來:先診斷、再壓縮、再快取、最後優化連線複用與緩衝。前提是你已用 Nginx 做 443 反代(若還沒配防火牆,建議先看 雲端伺服器 UFW 防火牆設定指南,只開 22/80/443 再談優化)。
為什麼頻寬會先爆,而不是 CPU?
雲端 VPS 的計費頻寬通常是雙向不對稱的:小套餐給 100 Mbps 峰值、月流量包 1–2 TB,看起來不少,但一個未壓縮的 500 KB JSON 介面,每秒 200 次請求就是 800 Mbps 的理論需求——瞬間打穿。與此同時,Nginx 處理靜態檔案和簡單反代,CPU 佔用往往很低,所以監控圖上會出現「CPU 很閒、頻寬很滿」的錯位。
另一個常見誤區是把所有流量都打到源站。沒有 CDN、沒有瀏覽器快取、沒有 proxy_cache,每個使用者每次重新整理都從 VPS 重新拉一遍 bundle.js 和 logo.png。Nginx 官方文件裡列出的 http 模組,本質上都是幫你「少傳位元組」或「少建連線」的工具箱——關鍵是按場景組合使用,而不是抄一份萬能設定就上線。
| 症狀 | 常見根因 | 優先動作 |
|---|---|---|
| API 回應慢、頻寬高 | JSON/HTML 未壓縮 | 開啟 gzip / brotli |
| 重新整理頁面流量大 | 靜態資源無 Cache-Control | expires + 指紋檔名 |
| 後端連線數飆高 | 每請求新建 upstream TCP | keepalive 連線池 |
| 大檔案下載占滿出口 | 源站直傳、無分段快取 | CDN 或 proxy_cache |
第一步:先診斷,別盲目升頻寬
動手改設定前,花十分鐘確認「頻寬花在哪兒」。我們在伺服器上常用這組指令:
# 即時按連線看流量(需 apt install iftop) sudo iftop -i eth0 # 按小時/天統計(vnstat) vnstat -h vnstat -d # Nginx 存取日誌:找體積最大的 URL awk '{print $7, $10}' /var/log/nginx/access.log | \ awk '{a[$1]+=$2} END {for(i in a) print a[i], i}' | sort -rn | head -20 # 目前 ESTABLISHED 連線數 ss -s ss -tn state established | wc -l
若日誌裡排前幾名的是 /api/ 且回應體動輒幾百 KB,壓縮是第一優先級;若是 .js、.css、.woff2 且狀態碼全是 200(沒有 304),表示快取標頭沒配好。還要區分入站和出站:DDoS 或爬蟲會把入站打滿,那是另一類問題(限速、WAF、fail2ban),本文聚焦正常業務下的出站優化。
雲端主控台裡的頻寬圖有 1–5 分鐘聚合延遲,排障時以伺服器本機 iftop 為準。若 SSH 都變卡,參考我們的 Linux 伺服器 SSH 連線被拒排查指南,先確認不是網路層故障。
第二步:Gzip / Brotli 壓縮——最低成本的頻寬削減
文字類回應(JSON、HTML、JS、CSS、SVG、XML)壓縮率通常在 60%–80%。Nginx 內建 gzip 模組,開起來只需幾行設定,對 CPU 的額外開銷在大多數 VPS 上完全可以接受。
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 5; # 6–9 收益遞減,5 是性價比甜點
gzip_min_length 256; # 太小的回應不必壓
gzip_types
text/plain
text/css
text/javascript
application/javascript
application/json
application/xml
image/svg+xml
font/woff2;
幾個實戰細節:第一,gzip_proxied any 確保反代後端回傳的內容也會壓縮(預設只壓直連回應)。第二,圖片(JPEG/PNG/WebP)和影片不要再 gzip——已經壓縮過的二進位,gzip 幾乎無效還浪費 CPU。第三,用 curl -H 'Accept-Encoding: gzip' -I https://your.site/api/foo 檢查回應標頭是否有 Content-Encoding: gzip。
若編譯時帶了 Brotli 模組(部分發行版或自編譯版本),同等品質下 Brotli 比 gzip 再省 15%–20%,適合文字 API 密集型站點。沒有模組也不必強求,先把 gzip 配穩。
第三步:靜態資源快取與 sendfile
前端建置工具(Vite、Webpack)會給檔名加 content hash,例如 app.a3f2b1.js。這種檔案可以長期快取,使用者第二次存取根本不走網路。問題出在很多團隊只做了建置側 hash,Nginx 側卻沒發 Cache-Control,瀏覽器每次仍發全量請求。
location ~* \.(js|css|woff2?|ttf|ico|svg)$ {
root /var/www/app/dist;
expires 30d;
add_header Cache-Control "public, immutable";
access_log off; # 靜態命中可關 access_log 減 IO
}
# 全域 http 區塊建議開啟
sendfile on;
tcp_nopush on;
tcp_nodelay on;
sendfile 讓核心直接把檔案頁快取送到 socket,減少使用者態拷貝,對大靜態檔案和高併發下載都有幫助。注意:sendfile 與 AIO、執行緒池等進階特性組合時要測一遍,預設 trio(sendfile + tcp_nopush + tcp_nodelay)對絕大多數站點夠用。
HTML 和 index.html 不要用 immutable,否則發版後使用者可能看到舊殼子。通常策略是:hash 資源 30 天,HTML no-cache(允許協商快取)。
第四步:反向代理快取(proxy_cache)
對讀多寫少的 API(設定列表、商品目錄、文章詳情),可以在 Nginx 層做短 TTL 快取,源站頻寬直接除以快取命中率。這是 proxy 模組裡最被低估的能力之一。
# http 區塊 proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=apicache:50m max_size=2g inactive=60m use_temp_path=off; upstream backend { server 127.0.0.1:3000; keepalive 32; # 見下一節 } server { location /api/ { proxy_pass http://backend; proxy_http_version 1.1; proxy_set_header Connection ""; proxy_cache apicache; proxy_cache_valid 200 5m; proxy_cache_key "$scheme$request_method$host$request_uri"; add_header X-Cache-Status $upstream_cache_status; proxy_buffering on; proxy_buffers 16 16k; proxy_busy_buffers_size 64k; } }
$upstream_cache_status 會顯示 HIT、MISS 或 BYPASS,排障時一眼可知快取是否生效。寫入操作(POST/PUT/DELETE)務必 bypass 快取;帶 Cookie 的個人化介面也要謹慎,避免把 A 使用者的資料快取給 B 使用者。
proxy_buffering 開啟時,Nginx 會先把後端回應收進緩衝區再發給客戶端——後端慢、客戶端快時,能避免後端連線長時間佔用;若後端是串流 SSE/大檔案上傳,要對特定 location 設 proxy_buffering off。
第五步:upstream keepalive 減少 TCP 握手開銷
預設情況下,Nginx 對每個客戶端請求可能新建一條到後端的 TCP 連線。高 QPS 時,大量 TIME_WAIT 和三次握手會吃掉 CPU 和本機連接埠,間接讓回應變慢、有效頻寬下降。upstream keepalive 讓 Nginx 維護一個到後端的連線池,請求完成後連線歸還池中複用。
upstream backend {
server 127.0.0.1:8080;
keepalive 64; # 池大小,按 QPS 調整
}
location / {
proxy_pass http://backend;
proxy_http_version 1.1; # HTTP/1.1 才能 keepalive
proxy_set_header Connection ""; # 清除 Connection: close
}
缺少 proxy_http_version 1.1 或 Connection "" 任一,keepalive 都不會生效——這是我們在客戶環境裡見過最多的「配了等於沒配」案例。池大小 keepalive 64 對日 PV 百萬級以下通常夠用,過大反而占後端檔案描述符。
第六步:worker、連線數與限速
頻寬打滿時,還要確認 Nginx 沒有成為連線數瓶頸。核心參數在 nginx.conf 頂層:
worker_processes auto;
worker_rlimit_nofile 65535;
events {
worker_connections 4096;
use epoll;
multi_accept on;
}
# 可選:按 IP 限速,防單使用者占滿頻寬
limit_req_zone $binary_remote_addr zone=perip:10m rate=10r/s;
limit_conn_zone $binary_remote_addr zone=conn:10m;
server {
limit_req zone=perip burst=20 nodelay;
limit_conn conn 50;
}
worker_connections × worker_processes 要大於峰值併發連線數。2 核機器設 worker_processes auto 通常是 2,4096 × 2 = 8192 對中小站點綽綽有餘。同時把系統 fs.file-max 和 worker_rlimit_nofile 對齊,否則日誌裡會出現 too many open files。
limit_rate 可對單連線限速(大檔案下載場景),limit_req 防刷介面。這些不直接「增加」頻寬,但能防止少數異常流量拖垮全體使用者——屬於頻寬治理的最後一道閘。
第七步:什麼時候該上 CDN?
當優化做完、頻寬仍週期性頂格,就該考慮 CDN 或升級套餐了。判斷標準很簡單:
- 靜態資源占出站 60% 以上 → CDN 快取 JS/CSS/圖片,源站只回源 HTML 和 API
- 使用者分布跨多個地區 → 邊緣節點縮短物理距離,等效降低源站頻寬壓力
- 流量有突發(行銷活動、直播) → CDN 扛峰值,避免源站按峰值頻寬買套餐
上 CDN 後 Nginx 側記得:源站只接受 CDN 回源 IP(防火牆白名單),並透傳真實客戶端 IP(X-Forwarded-For / real_ip 模組)。否則攻擊者直連源站 IP,CDN 的防護和快取就 bypass 了。
完整 server 區塊範例(可直接改網域部署)
把上文零散設定合成一份最小可執行範例,適合 Node / Python 後端監聽 127.0.0.1:3000 的場景:
upstream app {
server 127.0.0.1:3000;
keepalive 32;
}
server {
listen 443 ssl http2;
server_name app.example.com;
ssl_certificate /etc/letsencrypt/live/app.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/app.example.com/privkey.pem;
gzip on;
gzip_types application/json application/javascript text/css text/plain;
gzip_min_length 256;
location /assets/ {
alias /var/www/app/dist/assets/;
expires 30d;
add_header Cache-Control "public, immutable";
access_log off;
}
location / {
proxy_pass http://app;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Connection "";
}
}
改完後執行 sudo nginx -t && sudo systemctl reload nginx,用瀏覽器開發者工具 Network 面板確認靜態資源有 30 天快取、API 有 gzip。我們習慣在發版 checklist 裡加一條:curl -sI https://app.example.com/assets/main.js | grep -i cache。
驗證與持續監控
調校不是一錘子買賣。建議在 Grafana / 雲端監控裡同時看這四條線:出站頻寬、Nginx active connections、upstream 回應時間、快取命中率(若開了 proxy_cache)。發版或流量活動前做一次對比壓測:ab -n 1000 -c 50 https://app.example.com/ 或 hey -n 5000 -c 100,記錄調校前後的頻寬峰值和 P95 延遲。
若頻寬降下來了但延遲仍高,瓶頸可能轉到後端資料庫或磁碟 IO——那是另一個話題。但至少你可以確定:錢應該花在加索引上,而不是盲目買 500 Mbps 套餐。
調校之外:選對節點同樣省頻寬
Nginx 能幫你「少傳位元組」,但使用者到源站的物理距離沒法用設定消除。若你的受眾在亞太,卻買了一台美東 VPS,RTT 高、重傳多,有效吞吐天然打折。很多團隊會把建置與預覽放在雲端 Mac 或離使用者更近的節點,靜態資源走物件儲存 + CDN,源站只跑 API——頻寬帳單會好看很多。
VPSSPark 的 Mac mini M4 雲端節點適合跑 CI 打包、內網預覽和跳板維運:macOS 原生環境,Terminal 與 OpenSSH 開箱即用;待機功耗約 4W,適合 7×24 掛著當中轉或建置機,比再開一台 x86 小主機更省電。配合前文 UFW 只開 443、應用綁 127.0.0.1 的架構,攻擊面和頻寬浪費都能壓住。
如果你正在規劃一套「省頻寬、好維運」的部署架構, VPSSPark 雲端 Mac mini 是低功耗跳板與建置節點的務實選擇—— 立即了解套餐方案 ,讓 Nginx 調校成果不被錯誤的節點選址抵消。