Chuyển đến nội dung chính

第6課:Nginx 快取

Nginx 快取課程 — 瀏覽器快取(Expires 和 Cache-Control 標頭)、 代理快取、PHP 的 FastCGI 快取、快取鍵與快取區。 快取清除、繞過策略和最佳化技術指南。 含改善效能和降低負載的實際應用範例。

🔒 DevSecOps — 第6課 第6課:Nginx 快取 Nginx 從基礎到進階 第2部分:反向代理與負載均衡 xdev.asia

標籤

#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/bash

purge_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/bash

cache_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:瀏覽器快取

  1. 建立靜態檔案伺服器
  2. 設定 Expires 標頭:
    • 圖片:1 年
    • CSS/JS:1 個月
    • HTML:1 小時
  3. 使用瀏覽器 DevTools(網路分頁)進行測試
  4. 確認快取標頭

練習2:代理快取

  1. 建立後端伺服器(Node.js/Python)
  2. 設定 Nginx 代理快取
  3. 產生流量並監控快取命中/未命中
  4. 檢查 /var/cache/nginx 中的快取檔案

練習3:WordPress 的 FastCGI 快取

  1. 安裝 WordPress
  2. 設定 FastCGI 快取
  3. 測試以下情況的快取繞過:
    • 已登入使用者
    • 管理員頁面
    • POST 請求
  4. 測量效能改善

練習4:自訂快取鍵

  1. 設定帶有自訂鍵(含裝置類型)的快取
  2. 從行動裝置和桌機進行測試
  3. 驗證不同的快取版本

練習5:快取效能測試

  1. 分別建立有快取和無快取的環境
  2. 使用 Apache Bench 進行基準測試:
# 無快取
ab -n 1000 -c 10 http://example.com/

有快取

ab -n 1000 -c 10 http://example.com/

  1. 比較結果

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。