標籤
#Nginx #Caching #ProxyCache #FastCGI #BrowserCache #CacheControl #Expires #Performance #Optimization #CDN #CachePurging #CacheBypass #WebPerformance #StaticContent #DynamicContent #Tutorial #Production #BestPractices #DevOps #Infrastructure
1. 使用 Expires 與 Cache-Control 標頭的瀏覽器快取
瀏覽器快取將檔案儲存在客戶端瀏覽器,減少對伺服器的請求次數並加快載入速度。
1.1. Expires 標頭
Expires 標頭指定快取過期的具體時間點。
基本語法:
location ~* \.(jpg|jpeg|png|gif|ico)$ {
expires 30d; # 快取 30 天
}
Expires 值範例:
server { listen 80; server_name example.com; root /var/www/html;# 圖片 - 長期快取 location ~* \.(jpg|jpeg|png|gif|ico|svg|webp)$ { expires 1y; # 1 年 add_header Cache-Control "public, immutable"; } # CSS 和 JavaScript - 中期快取 location ~* \.(css|js)$ { expires 1M; # 1 個月 add_header Cache-Control "public"; } # HTML - 短期快取或不快取 location ~* \.html$ { expires 1h; # 1 小時 add_header Cache-Control "public, must-revalidate"; } # API 回應 - 不快取 location /api/ { expires -1; # 不快取 add_header Cache-Control "no-cache, no-store, must-revalidate"; }
}
時間單位:
expires 1s; # 1 秒 expires 5m; # 5 分鐘 expires 2h; # 2 小時 expires 7d; # 7 天 expires 3M; # 3 個月(30 天) expires 1y; # 1 年
expires -1; # 不快取(Cache-Control: no-cache) expires epoch; # Expires: Thu, 01 Jan 1970 00:00:01 GMT expires max; # Expires: Thu, 31 Dec 2037 23:55:55 GMT expires off; # 不設定 Expires 標頭
1.2. Cache-Control 標頭
Cache-Control 是 Expires 的現代替代方案,更靈活。
Cache-Control 指令:
# public: 可被瀏覽器和中間快取快取 add_header Cache-Control "public";private: 僅瀏覽器快取,代理不快取
add_header Cache-Control "private";
no-cache: 使用快取前必須重新驗證
add_header Cache-Control "no-cache";
no-store: 不儲存任何快取
add_header Cache-Control "no-store";
max-age: 快取期間(秒)
add_header Cache-Control "public, max-age=31536000";
s-maxage: 共享快取(CDN、代理)的 max-age
add_header Cache-Control "public, max-age=3600, s-maxage=86400";
must-revalidate: 過期後必須重新驗證
add_header Cache-Control "public, max-age=3600, must-revalidate";
immutable: 內容永不改變
add_header Cache-Control "public, max-age=31536000, immutable";
no-transform: 不允許內容轉換
add_header Cache-Control "public, no-transform";
組合指令:
server { listen 80; server_name example.com;# 有版本控制的靜態資源 - 不可變 location ~* /static/.*\.(css|js)$ { expires 1y; add_header Cache-Control "public, max-age=31536000, immutable"; } # 圖片 - 公開,長期快取 location ~* \.(jpg|png|gif|svg)$ { expires 6M; add_header Cache-Control "public, max-age=15552000"; } # HTML - 帶重新驗證的短期快取 location ~* \.html$ { expires 10m; add_header Cache-Control "public, max-age=600, must-revalidate"; } # 使用者特定內容 - 私有 location /dashboard/ { add_header Cache-Control "private, max-age=300"; } # 敏感資料 - 不快取 location /account/ { add_header Cache-Control "private, no-cache, no-store, must-revalidate"; add_header Pragma "no-cache"; add_header Expires "0"; }
}
1.3. 條件式快取
map $sent_http_content_type $expires { default off; text/html 1h; text/css 1M; application/javascript 1M; ~image/ 1y; application/pdf 7d; ~font/ 1y; }server { listen 80; server_name example.com;
expires $expires; # 特定路徑覆寫 location /news/ { expires 5m; add_header Cache-Control "public, max-age=300"; }
}
1.4. ETag 與 Last-Modified
server { listen 80; server_name example.com;# 啟用 ETag etag on; # 啟用 Last-Modified 標頭 if_modified_since before; location / { root /var/www/html; # 瀏覽器將發送 If-None-Match(ETag)或 If-Modified-Since # 若檔案未更改,Nginx 返回 304 Not Modified }
}
2. 代理快取基礎
代理快取儲存後端伺服器的回應,減少負載和回應時間。
2.1. 代理快取區設定
# 定義快取路徑和設定(在 http 上下文中) http { proxy_cache_path /var/cache/nginx/proxy levels=1:2 keys_zone=my_cache:10m max_size=1g inactive=60m use_temp_path=off;server { listen 80; server_name example.com; location / { proxy_pass http://backend; # 啟用快取 proxy_cache my_cache; # 快取有效期間 proxy_cache_valid 200 302 10m; proxy_cache_valid 404 1m; proxy_cache_valid any 5m; # 新增快取狀態標頭 add_header X-Cache-Status $upstream_cache_status; } }
}
參數說明:
proxy_cache_path /var/cache/nginx/proxy # 快取儲存路徑
levels=1:2 # 目錄結構(最佳化 I/O)
keys_zone=my_cache:10m # 區域名稱與共享記憶體大小
max_size=1g # 磁碟上的最大快取大小
inactive=60m # 60 分鐘未使用後刪除快取
use_temp_path=off; # 直接寫入快取路徑
2.2. 快取狀態值
$upstream_cache_status 可能的值:
- MISS : 快取中無此請求,從後端取得
- HIT : 從快取提供請求
- EXPIRED : 快取條目已過期,從後端取得
- STALE : 提供過期內容(若已設定)
- UPDATING : 快取正從後端更新中
- REVALIDATED: 快取已與後端重新驗證(304)
BYPASS : 快取已繞過(依設定)
2.3. 快取鍵
快取鍵決定快取項目的唯一性。
預設快取鍵:
proxy_cache_key $scheme$proxy_host$request_uri;範例:
http://example.com/page?id=1
鍵值:httpexample.com/page?id=1
自訂快取鍵:
location / { proxy_pass http://backend; proxy_cache my_cache;# 包含請求方法 proxy_cache_key "$scheme$request_method$host$request_uri"; # 包含特定標頭 # proxy_cache_key "$scheme$host$request_uri$http_accept_language"; # 包含 Cookie # proxy_cache_key "$scheme$host$request_uri$cookie_session";
}
2.4. 快取方法與條件
server { listen 80;location / { proxy_pass http://backend; proxy_cache my_cache; # 僅快取 GET 和 HEAD 方法 proxy_cache_methods GET HEAD; # 依狀態碼設定快取有效期間 proxy_cache_valid 200 301 302 10m; proxy_cache_valid 404 1m; proxy_cache_valid any 5m; # 快取前的最少請求次數 proxy_cache_min_uses 3; # 後端錯誤時提供過期快取 proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504; # 防止快取雪崩的鎖定機制 proxy_cache_lock on; proxy_cache_lock_timeout 5s; proxy_cache_lock_age 5s; }
}
2.5. 完整代理快取範例
http { # 定義多個快取區 proxy_cache_path /var/cache/nginx/static levels=1:2 keys_zone=static_cache:10m max_size=500m inactive=60m use_temp_path=off;proxy_cache_path /var/cache/nginx/api levels=1:2 keys_zone=api_cache:10m max_size=200m inactive=10m use_temp_path=off; upstream backend { server backend1.example.com:8080; server backend2.example.com:8080; } server { listen 80; server_name example.com; # 靜態內容快取 location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ { proxy_pass http://backend; proxy_cache static_cache; proxy_cache_valid 200 30d; proxy_cache_valid 404 1h; proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504; proxy_cache_lock on; expires 30d; add_header Cache-Control "public, immutable"; add_header X-Cache-Status $upstream_cache_status; } # API 快取 location /api/ { proxy_pass http://backend; proxy_cache api_cache; proxy_cache_valid 200 5m; proxy_cache_valid 404 1m; proxy_cache_key "$scheme$request_method$host$request_uri$http_authorization"; proxy_cache_methods GET HEAD; proxy_cache_min_uses 2; proxy_cache_use_stale error timeout updating; proxy_cache_lock on; add_header X-Cache-Status $upstream_cache_status; } # 使用者特定內容不快取 location /account/ { proxy_pass http://backend; proxy_cache off; proxy_no_cache 1; proxy_cache_bypass 1; } }
}
3. FastCGI 快取
FastCGI 快取用於 PHP 應用程式等動態內容。
3.1. FastCGI 快取設定
http { # 定義 FastCGI 快取區 fastcgi_cache_path /var/cache/nginx/fastcgi levels=1:2 keys_zone=php_cache:10m max_size=500m inactive=60m use_temp_path=off;# 快取鍵 fastcgi_cache_key "$scheme$request_method$host$request_uri"; server { listen 80; server_name example.com; root /var/www/html; index index.php index.html; location ~ \.php$ { fastcgi_pass unix:/var/run/php/php8.1-fpm.sock; fastcgi_index index.php; include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; # 啟用 FastCGI 快取 fastcgi_cache php_cache; # 快取有效期間 fastcgi_cache_valid 200 60m; fastcgi_cache_valid 404 10m; # 提供過期快取的條件 fastcgi_cache_use_stale error timeout updating invalid_header http_500; # 快取鎖定 fastcgi_cache_lock on; fastcgi_cache_lock_timeout 5s; # 快取前的最少使用次數 fastcgi_cache_min_uses 2; # 新增快取狀態標頭 add_header X-FastCGI-Cache $upstream_cache_status; } }
}
3.2. WordPress 與 FastCGI 快取
http { fastcgi_cache_path /var/cache/nginx/wordpress levels=1:2 keys_zone=wordpress:100m max_size=1g inactive=60m use_temp_path=off;fastcgi_cache_key "$scheme$request_method$host$request_uri"; server { listen 80; server_name blog.example.com; root /var/www/wordpress; index index.php; # 設定快取繞過條件 set $skip_cache 0; # POST 請求和含查詢字串的 URL if ($request_method = POST) { set $skip_cache 1; } if ($query_string != "") { set $skip_cache 1; } # 不快取包含以下路徑的 URI if ($request_uri ~* "/wp-admin/|/xmlrpc.php|wp-.*.php|/feed/|index.php|sitemap(_index)?.xml") { set $skip_cache 1; } # 不快取已登入使用者或最近留言者 if ($http_cookie ~* "comment_author|wordpress_[a-f0-9]+|wp-postpass|wordpress_logged_in") { set $skip_cache 1; } location / { try_files $uri $uri/ /index.php?$args; } location ~ \.php$ { try_files $uri =404; fastcgi_split_path_info ^(.+\.php)(/.+)$; fastcgi_pass unix:/var/run/php/php8.1-fpm.sock; fastcgi_index index.php; include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_param PATH_INFO $fastcgi_path_info; fastcgi_cache wordpress; fastcgi_cache_valid 200 60m; fastcgi_cache_valid 404 10m; fastcgi_cache_bypass $skip_cache; fastcgi_no_cache $skip_cache; fastcgi_cache_use_stale error timeout updating invalid_header http_500 http_503; fastcgi_cache_lock on; add_header X-FastCGI-Cache $upstream_cache_status; add_header Cache-Control "public, max-age=3600"; } # 靜態檔案快取 location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2)$ { expires 30d; add_header Cache-Control "public, immutable"; access_log off; } }
}
4. 快取鍵與快取區
4.1. 快取區設定
http { # 靜態內容快取 - 大容量,長期 proxy_cache_path /var/cache/nginx/static levels=1:2 keys_zone=static:100m max_size=5g inactive=7d use_temp_path=off;# API 快取 - 小容量,短期 proxy_cache_path /var/cache/nginx/api levels=1:2 keys_zone=api:50m max_size=1g inactive=1h use_temp_path=off; # 使用者特定快取 proxy_cache_path /var/cache/nginx/user levels=1:2 keys_zone=user:50m max_size=2g inactive=30m use_temp_path=off;
}
區域大小計算:
keys_zone 大小儲存快取元資料:
- 1MB ≈ 8,000 個鍵值
- 10MB ≈ 80,000 個鍵值
- 100MB ≈ 800,000 個鍵值
max_size 儲存實際內容:
- 依可用磁碟空間設定
監控 /var/cache/nginx 的磁碟使用量
4.2. 進階快取鍵
依裝置類型快取:
map $http_user_agent $device { default desktop; ~mobile mobile; ~tablet tablet; }server { location / { proxy_pass http://backend; proxy_cache my_cache;
# 不同裝置使用不同快取 proxy_cache_key "$scheme$host$request_uri$device"; add_header X-Device $device; add_header X-Cache-Status $upstream_cache_status; }
}
依語言快取:
map $http_accept_language $lang { default en; ~^vi vi; ~^ja ja; ~*^zh zh; }server { location / { proxy_pass http://backend; proxy_cache my_cache;
# 不同語言使用不同快取 proxy_cache_key "$scheme$host$request_uri$lang"; add_header Content-Language $lang; }
}
複合快取鍵:
map $http_cookie $user_segment { default "guest"; ~premium=1 "premium"; ~vip=1 "vip"; }server { location /api/ { proxy_pass http://backend; proxy_cache api_cache;
# 結合多個因素的快取鍵 proxy_cache_key "$scheme$host$request_uri$user_segment$device$lang"; add_header X-Cache-Key-Components "segment:$user_segment|device:$device|lang:$lang"; }
}
4.3. 快取階層
http { # L1 快取 - 記憶體(小容量,高速) proxy_cache_path /dev/shm/nginx levels=1 keys_zone=l1_cache:10m max_size=100m inactive=5m use_temp_path=off;# L2 快取 - SSD(中容量,高速) proxy_cache_path /var/cache/nginx/l2 levels=1:2 keys_zone=l2_cache:50m max_size=5g inactive=1h use_temp_path=off; # L3 快取 - HDD(大容量,較慢) proxy_cache_path /mnt/cache/nginx/l3 levels=1:2 keys_zone=l3_cache:100m max_size=50g inactive=7d use_temp_path=off; server { location /api/hot/ { proxy_pass http://backend; proxy_cache l1_cache; proxy_cache_valid 200 5m; } location /api/warm/ { proxy_pass http://backend; proxy_cache l2_cache; proxy_cache_valid 200 1h; } location /api/cold/ { proxy_pass http://backend; proxy_cache l3_cache; proxy_cache_valid 200 1d; } }
}
5. 快取清除與繞過
5.1. 快取繞過
server { listen 80;location / { proxy_pass http://backend; proxy_cache my_cache; # 使用特殊標頭繞過快取 proxy_cache_bypass $http_x_no_cache; # 或使用 Cookie 繞過 proxy_cache_bypass $cookie_nocache; # 或使用查詢參數繞過 proxy_cache_bypass $arg_nocache; }
}
多個繞過條件:
server { location / { proxy_pass http://backend; proxy_cache my_cache;set $cache_bypass 0; # 特定 Cookie 時繞過 if ($http_cookie ~* "admin_logged_in") { set $cache_bypass 1; } # 特定 URL 時繞過 if ($request_uri ~* "^/(admin|dashboard)/") { set $cache_bypass 1; } # POST 請求時繞過 if ($request_method = POST) { set $cache_bypass 1; } proxy_cache_bypass $cache_bypass; proxy_no_cache $cache_bypass; }
}
5.2. 快取清除(手動腳本)
#!/bin/bashpurge_cache.sh - 手動快取清除腳本
CACHE_DIR="/var/cache/nginx" CACHE_ZONE="my_cache"
清除所有快取
purge_all() { echo "正在清除所有快取..." sudo rm -rf ${CACHE_DIR}/${CACHE_ZONE}/* echo "快取已清除!" }
清除特定 URL 的快取
purge_url() { local url=$1 local cache_key=$(echo -n "$url" | md5sum | awk '{print $1}') local cache_path=$(find ${CACHE_DIR}/${CACHE_ZONE} -name "${cache_key}")
if [ -n "$cache_path" ]; then echo "正在清除快取:$url" sudo rm -f $cache_path echo "已清除:$cache_path" else echo "找不到快取:$url" fi}
case "$1" in all) purge_all ;; url) purge_url "$2" ;; *) echo "用法:$0 {all|url}" exit 1 ;; esac
快取預熱腳本:
#!/bin/bashcache_warmup.sh - 快取預熱
URLS=( "http://example.com/" "http://example.com/products" "http://example.com/about" "http://example.com/contact" )
echo "開始快取預熱..."
for url in "${URLS[@]}"; do echo "預熱中:$url" curl -s -o /dev/null -w "狀態:%{http_code},時間:%{time_total}秒\n" "$url" sleep 0.5 done
echo "快取預熱完成!"
6. 練習題
練習1:瀏覽器快取
- 建立靜態檔案伺服器
- 設定 Expires 標頭:
- 圖片:1 年
- CSS/JS:1 個月
- HTML:1 小時
- 使用瀏覽器 DevTools(網路分頁)進行測試
- 確認快取標頭
練習2:代理快取
- 建立後端伺服器(Node.js/Python)
- 設定 Nginx 代理快取
- 產生流量並監控快取命中/未命中
- 檢查
/var/cache/nginx中的快取檔案
練習3:WordPress 的 FastCGI 快取
- 安裝 WordPress
- 設定 FastCGI 快取
- 測試以下情況的快取繞過:
- 已登入使用者
- 管理員頁面
- POST 請求
- 測量效能改善
練習4:自訂快取鍵
- 設定帶有自訂鍵(含裝置類型)的快取
- 從行動裝置和桌機進行測試
- 驗證不同的快取版本
練習5:快取效能測試
- 分別建立有快取和無快取的環境
- 使用 Apache Bench 進行基準測試:
# 無快取 ab -n 1000 -c 10 http://example.com/有快取
ab -n 1000 -c 10 http://example.com/
- 比較結果
7. 故障排除
7.1. 快取無法運作
問題: X-Cache-Status 始終顯示 MISS
診斷:
# 檢查快取目錄權限 ls -la /var/cache/nginx/檢查快取設定
sudo nginx -T | grep cache
確認快取區是否已定義
sudo nginx -T | grep keys_zone
監控快取檔案建立情況
watch -n 1 'ls -lh /var/cache/nginx/proxy/'
解決方案:
# 設定適當的權限 sudo chown -R nginx:nginx /var/cache/nginx/ sudo chmod -R 755 /var/cache/nginx/確認快取區設定
http { proxy_cache_path /var/cache/nginx/proxy levels=1:2 keys_zone=my_cache:10m;
server { location / { proxy_pass http://backend; proxy_cache my_cache; # 必須與 keys_zone 名稱一致 proxy_cache_valid 200 10m; } }
}
7.2. 快取佔用過多磁碟空間
解決方案:
# 設定 max_size proxy_cache_path /var/cache/nginx/proxy levels=1:2 keys_zone=my_cache:10m max_size=1g # 限制為 1GB inactive=60m; # 清除非活躍檔案或使用 tmpfs(RAM 磁碟)
新增至 /etc/fstab:
tmpfs /var/cache/nginx tmpfs defaults,size=512M 0 0
7.3. 提供了過期內容
解決方案:
# 手動清除快取 sudo rm -rf /var/cache/nginx/proxy/* sudo systemctl reload nginx或實作快取版本控制
location / { proxy_pass http://backend; proxy_cache my_cache;
# 在快取鍵中加入版本號 proxy_cache_key "$scheme$host$request_uri$http_x_app_version";
}
總結
本課學到了:
- ✅ 使用 Expires 與 Cache-Control 標頭的瀏覽器快取
- ✅ 代理快取設定(快取區、鍵值、有效期間)
- ✅ PHP/WordPress 的 FastCGI 快取
- ✅ 進階快取鍵策略(依裝置、語言、使用者區段)
- ✅ 快取繞過和清除方法
- ✅ 快取效能故障排除
下一課: SSL/TLS — 如何使用 Let's Encrypt 設定 HTTPS、實作安全通訊與 HSTS。