Ubuntu 22.04 Nginx 速率限制設定

Ubuntu 22.04 Nginx Rate Limiting Configuration

在現代網路環境中,保護網站伺服器免受惡意攻擊和過量請求是至關重要的。Nginx 提供了強大的速率限制(Rate Limiting)功能,可以有效控制客戶端請求頻率,防止 DDoS 攻擊、暴力破解和資源濫用。本文將詳細介紹如何在 Ubuntu 22.04 上設定 Nginx 速率限制。

速率限制概念與用途

什麼是速率限制?

速率限制是一種控制機制,用於限制特定時間內來自單一來源的請求數量。當請求超過設定的閾值時,Nginx 會拒絕額外的請求或將其排入佇列等待處理。

速率限制的主要用途

  • 防止 DDoS 攻擊:限制每個 IP 的請求頻率,減輕分散式阻斷服務攻擊的影響
  • 保護 API 端點:避免 API 被過度使用或濫用
  • 防止暴力破解:限制登入嘗試次數,保護使用者帳戶安全
  • 資源保護:確保伺服器資源公平分配給所有使用者
  • 成本控制:避免因流量激增而產生意外的頻寬費用

前置準備

確保您的 Ubuntu 22.04 系統已安裝 Nginx:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# 更新套件清單
sudo apt update

# 安裝 Nginx
sudo apt install nginx -y

# 確認 Nginx 版本
nginx -v

# 啟動並設定開機自動啟動
sudo systemctl start nginx
sudo systemctl enable nginx

limit_req_zone 與 limit_req 指令

Nginx 的請求速率限制主要透過兩個指令來實現:limit_req_zonelimit_req

limit_req_zone 指令

limit_req_zone 用於定義共享記憶體區域和速率限制規則,必須在 http 區塊中設定。

語法

1
limit_req_zone key zone=name:size rate=rate;

參數說明

  • key:用於識別請求的鍵值,通常使用 $binary_remote_addr(客戶端 IP 的二進位格式)
  • zone:共享記憶體區域的名稱和大小
  • rate:允許的請求速率(每秒或每分鐘)

範例配置

編輯 Nginx 主配置檔案:

1
sudo nano /etc/nginx/nginx.conf

http 區塊中加入:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
http {
    # 定義速率限制區域
    # 每個 IP 每秒最多 10 個請求
    limit_req_zone $binary_remote_addr zone=req_limit:10m rate=10r/s;

    # 每個 IP 每分鐘最多 30 個請求(適用於 API)
    limit_req_zone $binary_remote_addr zone=api_limit:10m rate=30r/m;

    # 基於 URI 的速率限制
    limit_req_zone $request_uri zone=uri_limit:10m rate=1r/s;

    # 組合鍵值(IP + URI)
    limit_req_zone $binary_remote_addr$request_uri zone=combined_limit:10m rate=5r/s;

    # 其他配置...
}

記憶體大小說明

  • 1MB 約可儲存 16,000 個 IP 地址的狀態
  • 10MB 可處理約 160,000 個不同的 IP 地址

limit_req 指令

limit_req 用於在特定位置啟用速率限制,可在 httpserverlocation 區塊中使用。

語法

1
limit_req zone=name [burst=number] [nodelay | delay=number];

基本範例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
server {
    listen 80;
    server_name example.com;

    # 對整個網站啟用速率限制
    limit_req zone=req_limit;

    location /api/ {
        # 對 API 端點啟用更嚴格的速率限制
        limit_req zone=api_limit;
        proxy_pass http://backend;
    }

    location /login {
        # 登入頁面使用較嚴格的限制
        limit_req zone=api_limit;
    }
}

Burst 與 nodelay 參數

Burst 參數

burst 參數允許請求在短時間內超過設定的速率,這些超額請求會被排入佇列等待處理,而不是立即被拒絕。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
http {
    limit_req_zone $binary_remote_addr zone=req_limit:10m rate=10r/s;
}

server {
    location / {
        # 允許最多 20 個請求排隊
        limit_req zone=req_limit burst=20;
    }
}

工作原理

  • 速率設定為 10r/s,表示每 100ms 處理一個請求
  • burst=20 表示最多可以有 20 個請求在佇列中等待
  • 當佇列滿時,新的請求將收到 503 錯誤

nodelay 參數

預設情況下,佇列中的請求會按照設定的速率逐一處理,這可能導致明顯的延遲。nodelay 參數可以讓佇列中的請求立即處理。

1
2
3
4
5
6
server {
    location / {
        # 立即處理 burst 中的請求,不延遲
        limit_req zone=req_limit burst=20 nodelay;
    }
}

使用 nodelay 的效果

  • 佇列中的請求會立即被處理
  • 但仍會計入速率限制的計算
  • 適合需要快速回應的應用場景

delay 參數

Nginx 1.15.7 之後引入了 delay 參數,提供更精細的控制:

1
2
3
4
5
6
server {
    location / {
        # 前 12 個超額請求立即處理,之後的請求需要排隊
        limit_req zone=req_limit burst=20 delay=12;
    }
}

完整配置範例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
http {
    # 定義多個速率限制區域
    limit_req_zone $binary_remote_addr zone=general:10m rate=10r/s;
    limit_req_zone $binary_remote_addr zone=strict:10m rate=1r/s;
    limit_req_zone $binary_remote_addr zone=api:10m rate=100r/m;

    server {
        listen 80;
        server_name example.com;

        # 一般頁面:較寬鬆的限制
        location / {
            limit_req zone=general burst=20 nodelay;
            root /var/www/html;
        }

        # 敏感操作:嚴格限制
        location /admin/ {
            limit_req zone=strict burst=5;
            proxy_pass http://admin_backend;
        }

        # API 端點:適中的限制
        location /api/v1/ {
            limit_req zone=api burst=10 delay=5;
            proxy_pass http://api_backend;
        }
    }
}

連線數限制(limit_conn)

除了請求速率限制外,Nginx 還提供連線數限制功能,用於限制同時連線的數量。

limit_conn_zone 指令

1
2
3
4
5
6
7
http {
    # 定義連線數限制區域
    limit_conn_zone $binary_remote_addr zone=conn_limit:10m;

    # 基於伺服器的連線限制
    limit_conn_zone $server_name zone=server_conn:10m;
}

limit_conn 指令

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
server {
    listen 80;
    server_name example.com;

    # 每個 IP 最多 10 個同時連線
    limit_conn conn_limit 10;

    # 每個伺服器最多 1000 個同時連線
    limit_conn server_conn 1000;

    location /download/ {
        # 下載區域限制每個 IP 最多 2 個同時連線
        limit_conn conn_limit 2;

        # 限制每個連線的下載速度為 500KB/s
        limit_rate 500k;
    }
}

結合速率限制與連線數限制

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
http {
    # 速率限制區域
    limit_req_zone $binary_remote_addr zone=req_limit:10m rate=10r/s;

    # 連線數限制區域
    limit_conn_zone $binary_remote_addr zone=conn_limit:10m;

    server {
        listen 80;
        server_name example.com;

        # 同時啟用兩種限制
        limit_req zone=req_limit burst=20 nodelay;
        limit_conn conn_limit 20;

        location /api/ {
            limit_req zone=req_limit burst=10 nodelay;
            limit_conn conn_limit 5;
            proxy_pass http://api_backend;
        }
    }
}

多層速率限制策略

在實際應用中,通常需要針對不同的端點和使用場景設定多層速率限制策略。

分層限制策略

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
http {
    # 全域基礎限制
    limit_req_zone $binary_remote_addr zone=global:10m rate=50r/s;

    # 登入相關的嚴格限制
    limit_req_zone $binary_remote_addr zone=login:10m rate=5r/m;

    # API 限制
    limit_req_zone $binary_remote_addr zone=api:10m rate=100r/m;

    # 靜態資源的寬鬆限制
    limit_req_zone $binary_remote_addr zone=static:10m rate=200r/s;

    # 連線數限制
    limit_conn_zone $binary_remote_addr zone=conn_per_ip:10m;
    limit_conn_zone $server_name zone=conn_per_server:10m;

    server {
        listen 80;
        server_name example.com;

        # 全域限制
        limit_req zone=global burst=100 nodelay;
        limit_conn conn_per_ip 50;
        limit_conn conn_per_server 10000;

        # 登入頁面 - 最嚴格的限制
        location = /login {
            limit_req zone=login burst=3;
            limit_conn conn_per_ip 3;
            proxy_pass http://auth_backend;
        }

        # 密碼重設 - 同樣嚴格
        location = /reset-password {
            limit_req zone=login burst=2;
            limit_conn conn_per_ip 2;
            proxy_pass http://auth_backend;
        }

        # API 端點
        location /api/ {
            limit_req zone=api burst=20 delay=10;
            limit_conn conn_per_ip 10;
            proxy_pass http://api_backend;
        }

        # 靜態資源
        location /static/ {
            limit_req zone=static burst=50 nodelay;
            alias /var/www/static/;
        }

        # 圖片資源
        location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ {
            limit_req zone=static burst=100 nodelay;
            expires 1y;
            add_header Cache-Control "public, immutable";
        }
    }
}

基於請求方法的限制

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
http {
    # GET 請求的限制(較寬鬆)
    limit_req_zone $binary_remote_addr zone=get_limit:10m rate=30r/s;

    # POST 請求的限制(較嚴格)
    limit_req_zone $binary_remote_addr zone=post_limit:10m rate=5r/s;

    server {
        listen 80;
        server_name example.com;

        location /api/ {
            # 使用 map 或 if 來區分請求方法
            limit_req zone=get_limit burst=50 nodelay;

            if ($request_method = POST) {
                set $limit_rate_zone "post";
            }

            proxy_pass http://api_backend;
        }
    }
}

# 更優雅的方式:使用 map
http {
    map $request_method $limit_key {
        GET     $binary_remote_addr;
        POST    $binary_remote_addr;
        default $binary_remote_addr;
    }

    limit_req_zone $limit_key zone=method_limit:10m rate=10r/s;
}

白名單與例外處理

在某些情況下,您需要將特定 IP 或用戶端排除在速率限制之外。

使用 geo 模組設定白名單

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
http {
    # 定義白名單
    geo $whitelist {
        default 0;

        # 本地主機
        127.0.0.1 1;

        # 內部網路
        10.0.0.0/8 1;
        192.168.0.0/16 1;
        172.16.0.0/12 1;

        # 特定的信任 IP
        203.0.113.50 1;
        198.51.100.0/24 1;

        # 監控服務 IP
        203.0.113.100 1;
    }

    # 基於白名單的速率限制鍵值
    map $whitelist $limit_key {
        0 $binary_remote_addr;
        1 "";  # 空字串表示不限制
    }

    # 使用條件式鍵值
    limit_req_zone $limit_key zone=req_limit:10m rate=10r/s;
    limit_conn_zone $limit_key zone=conn_limit:10m;

    server {
        listen 80;
        server_name example.com;

        # 白名單 IP 不受限制
        limit_req zone=req_limit burst=20 nodelay;
        limit_conn conn_limit 20;

        location / {
            root /var/www/html;
        }
    }
}

基於 Header 的白名單

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
http {
    # 檢查 API Key
    map $http_x_api_key $api_limit_key {
        default $binary_remote_addr;
        "your-premium-api-key-1" "";
        "your-premium-api-key-2" "";
    }

    limit_req_zone $api_limit_key zone=api_limit:10m rate=100r/m;

    server {
        listen 80;
        server_name api.example.com;

        location /api/ {
            limit_req zone=api_limit burst=20;
            proxy_pass http://api_backend;
        }
    }
}

分級速率限制

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
http {
    # 根據 API 層級設定不同的限制
    map $http_x_api_tier $rate_tier {
        default     "basic";
        "premium"   "premium";
        "enterprise" "enterprise";
    }

    # 基礎用戶:100 請求/分鐘
    limit_req_zone $binary_remote_addr zone=basic:10m rate=100r/m;

    # 進階用戶:1000 請求/分鐘
    limit_req_zone $binary_remote_addr zone=premium:10m rate=1000r/m;

    # 企業用戶:10000 請求/分鐘
    limit_req_zone $binary_remote_addr zone=enterprise:10m rate=10000r/m;

    server {
        listen 80;
        server_name api.example.com;

        location /api/ {
            # 根據層級套用不同的限制
            if ($rate_tier = "basic") {
                limit_req zone=basic burst=10;
            }
            if ($rate_tier = "premium") {
                limit_req zone=premium burst=100;
            }
            if ($rate_tier = "enterprise") {
                limit_req zone=enterprise burst=1000;
            }

            proxy_pass http://api_backend;
        }
    }
}

日誌與監控

有效的日誌記錄和監控對於調整速率限制策略至關重要。

設定速率限制日誌

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
http {
    # 自訂日誌格式,包含速率限制資訊
    log_format rate_limit '$remote_addr - $remote_user [$time_local] '
                          '"$request" $status $body_bytes_sent '
                          '"$http_referer" "$http_user_agent" '
                          'rt=$request_time uct="$upstream_connect_time" '
                          'limit_req_status=$limit_req_status';

    # 速率限制區域
    limit_req_zone $binary_remote_addr zone=req_limit:10m rate=10r/s;

    # 設定被限制時的日誌級別
    limit_req_log_level warn;  # 可選:info, notice, warn, error

    # 設定被拒絕請求的狀態碼
    limit_req_status 429;  # 預設是 503,建議使用 429 (Too Many Requests)
    limit_conn_status 429;

    server {
        listen 80;
        server_name example.com;

        # 使用自訂日誌格式
        access_log /var/log/nginx/access.log rate_limit;
        error_log /var/log/nginx/error.log warn;

        # 專門記錄被限制的請求
        access_log /var/log/nginx/rate_limited.log rate_limit if=$limit_req_status;

        limit_req zone=req_limit burst=20 nodelay;

        location / {
            root /var/www/html;
        }
    }
}

建立監控腳本

建立一個監控腳本來追蹤被限制的請求:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#!/bin/bash
# /usr/local/bin/nginx-rate-limit-monitor.sh

LOG_FILE="/var/log/nginx/error.log"
ALERT_THRESHOLD=100
EMAIL="admin@example.com"

# 計算過去 5 分鐘內被限制的請求數
COUNT=$(grep "limiting requests" $LOG_FILE | \
        awk -v date="$(date -d '5 minutes ago' '+%Y/%m/%d %H:%M')" \
        '$0 >= date' | wc -l)

echo "過去 5 分鐘被限制的請求數: $COUNT"

# 如果超過閾值,發送警報
if [ $COUNT -gt $ALERT_THRESHOLD ]; then
    echo "警告:速率限制觸發次數過高!" | \
    mail -s "Nginx Rate Limit Alert" $EMAIL
fi

# 顯示被限制最多的 IP
echo "被限制最多的 IP 地址:"
grep "limiting requests" $LOG_FILE | \
    grep -oE '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+' | \
    sort | uniq -c | sort -rn | head -10

設定定時執行:

1
2
3
4
5
# 設定 crontab
sudo crontab -e

# 每 5 分鐘執行一次監控
*/5 * * * * /usr/local/bin/nginx-rate-limit-monitor.sh >> /var/log/nginx/rate-limit-monitor.log 2>&1

使用 Prometheus 和 Grafana 監控

安裝 nginx-prometheus-exporter:

1
2
3
4
5
6
7
# 下載並安裝 nginx-prometheus-exporter
wget https://github.com/nginxinc/nginx-prometheus-exporter/releases/download/v0.11.0/nginx-prometheus-exporter_0.11.0_linux_amd64.tar.gz
tar xzf nginx-prometheus-exporter_0.11.0_linux_amd64.tar.gz
sudo mv nginx-prometheus-exporter /usr/local/bin/

# 啟用 Nginx stub_status
sudo nano /etc/nginx/conf.d/status.conf
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# /etc/nginx/conf.d/status.conf
server {
    listen 127.0.0.1:8080;

    location /nginx_status {
        stub_status on;
        allow 127.0.0.1;
        deny all;
    }
}

即時監控命令

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# 即時監控被拒絕的請求
sudo tail -f /var/log/nginx/error.log | grep --line-buffered "limiting"

# 統計每分鐘被限制的請求數
watch -n 60 'grep "limiting requests" /var/log/nginx/error.log | \
    grep "$(date "+%Y/%m/%d %H:%M")" | wc -l'

# 查看目前連線狀態
ss -s

# 查看每個 IP 的連線數
netstat -ntu | awk '{print $5}' | cut -d: -f1 | sort | uniq -c | sort -rn | head -20

最佳實務與調校

速率限制設定建議

1. 從寬鬆開始,逐步收緊

1
2
3
4
5
# 初始設定:較寬鬆的限制
limit_req_zone $binary_remote_addr zone=req_limit:10m rate=50r/s;

# 監控一段時間後,根據實際流量調整
# limit_req_zone $binary_remote_addr zone=req_limit:10m rate=20r/s;

2. 針對不同端點設定不同限制

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
http {
    # 靜態資源:高限制
    limit_req_zone $binary_remote_addr zone=static:10m rate=100r/s;

    # 動態頁面:中等限制
    limit_req_zone $binary_remote_addr zone=dynamic:10m rate=20r/s;

    # API:根據成本設定
    limit_req_zone $binary_remote_addr zone=api:10m rate=60r/m;

    # 認證相關:嚴格限制
    limit_req_zone $binary_remote_addr zone=auth:10m rate=10r/m;
}

3. 使用適當的錯誤頁面

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
server {
    # 設定速率限制狀態碼
    limit_req_status 429;
    limit_conn_status 429;

    # 自訂錯誤頁面
    error_page 429 /429.html;

    location = /429.html {
        root /var/www/error;
        internal;
    }
}

建立自訂錯誤頁面:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<!-- /var/www/error/429.html -->
<!DOCTYPE html>
<html lang="zh-TW">
<head>
    <meta charset="UTF-8">
    <title>請求過於頻繁</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            display: flex;
            justify-content: center;
            align-items: center;
            height: 100vh;
            margin: 0;
            background-color: #f5f5f5;
        }
        .container {
            text-align: center;
            padding: 40px;
            background: white;
            border-radius: 10px;
            box-shadow: 0 2px 10px rgba(0,0,0,0.1);
        }
        h1 { color: #e74c3c; }
        p { color: #666; }
    </style>
</head>
<body>
    <div class="container">
        <h1>429 - 請求過於頻繁</h1>
        <p>您的請求次數過多,請稍後再試。</p>
        <p>如果您認為這是錯誤,請聯繫網站管理員。</p>
    </div>
</body>
</html>

4. 完整的生產環境配置範例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# /etc/nginx/nginx.conf
user www-data;
worker_processes auto;
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;

events {
    worker_connections 1024;
    use epoll;
    multi_accept on;
}

http {
    # 基本設定
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    keepalive_timeout 65;
    types_hash_max_size 2048;

    include /etc/nginx/mime.types;
    default_type application/octet-stream;

    # 日誌設定
    log_format main '$remote_addr - $remote_user [$time_local] "$request" '
                    '$status $body_bytes_sent "$http_referer" '
                    '"$http_user_agent" "$http_x_forwarded_for"';

    log_format rate_limit '$remote_addr - [$time_local] "$request" '
                          '$status limit_req=$limit_req_status';

    access_log /var/log/nginx/access.log main;
    error_log /var/log/nginx/error.log warn;

    # 白名單設定
    geo $whitelist {
        default 0;
        127.0.0.1 1;
        10.0.0.0/8 1;
        192.168.0.0/16 1;
    }

    map $whitelist $limit_key {
        0 $binary_remote_addr;
        1 "";
    }

    # 速率限制區域
    limit_req_zone $limit_key zone=general:10m rate=30r/s;
    limit_req_zone $limit_key zone=api:10m rate=100r/m;
    limit_req_zone $limit_key zone=auth:10m rate=10r/m;
    limit_req_zone $limit_key zone=static:10m rate=100r/s;

    # 連線數限制區域
    limit_conn_zone $limit_key zone=conn_per_ip:10m;
    limit_conn_zone $server_name zone=conn_per_server:10m;

    # 限制設定
    limit_req_log_level warn;
    limit_req_status 429;
    limit_conn_status 429;

    # Gzip 壓縮
    gzip on;
    gzip_vary on;
    gzip_proxied any;
    gzip_comp_level 6;
    gzip_types text/plain text/css text/xml application/json application/javascript
               application/xml application/xml+rss text/javascript;

    # 載入網站配置
    include /etc/nginx/conf.d/*.conf;
    include /etc/nginx/sites-enabled/*;
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
# /etc/nginx/sites-available/example.com
server {
    listen 80;
    listen [::]:80;
    server_name example.com www.example.com;

    # 重導向到 HTTPS
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name example.com www.example.com;

    # SSL 設定
    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
    ssl_prefer_server_ciphers off;

    # 文件根目錄
    root /var/www/example.com;
    index index.html index.htm;

    # 全域速率限制
    limit_req zone=general burst=50 nodelay;
    limit_conn conn_per_ip 30;
    limit_conn conn_per_server 10000;

    # 日誌
    access_log /var/log/nginx/example.com.access.log main;
    access_log /var/log/nginx/example.com.ratelimit.log rate_limit if=$limit_req_status;
    error_log /var/log/nginx/example.com.error.log warn;

    # 錯誤頁面
    error_page 429 /429.html;
    location = /429.html {
        root /var/www/error;
        internal;
    }

    # 認證端點 - 最嚴格的限制
    location ~ ^/(login|register|reset-password|forgot-password)$ {
        limit_req zone=auth burst=3;
        limit_conn conn_per_ip 3;
        try_files $uri $uri/ =404;
    }

    # API 端點
    location /api/ {
        limit_req zone=api burst=20 delay=10;
        limit_conn conn_per_ip 10;

        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        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_cache_bypass $http_upgrade;
    }

    # 靜態資源
    location /static/ {
        limit_req zone=static burst=100 nodelay;
        alias /var/www/example.com/static/;
        expires 1y;
        add_header Cache-Control "public, immutable";
    }

    # 圖片和媒體檔案
    location ~* \.(jpg|jpeg|png|gif|ico|svg|webp|mp4|webm)$ {
        limit_req zone=static burst=100 nodelay;
        expires 1y;
        add_header Cache-Control "public, immutable";
    }

    # CSS 和 JavaScript
    location ~* \.(css|js)$ {
        limit_req zone=static burst=100 nodelay;
        expires 1y;
        add_header Cache-Control "public, immutable";
    }

    # 預設位置
    location / {
        try_files $uri $uri/ =404;
    }
}

測試與驗證

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
# 測試配置語法
sudo nginx -t

# 重新載入配置
sudo systemctl reload nginx

# 使用 ab (Apache Benchmark) 測試速率限制
ab -n 100 -c 10 http://example.com/

# 使用 wrk 進行更精確的測試
wrk -t4 -c100 -d30s http://example.com/

# 使用 curl 測試單一請求
for i in {1..20}; do
    curl -s -o /dev/null -w "%{http_code}\n" http://example.com/
done

# 檢查日誌中的限制記錄
sudo grep "limiting" /var/log/nginx/error.log | tail -20

常見問題排解

  1. 速率限制不生效

    • 確認 limit_req_zonehttp 區塊中定義
    • 檢查白名單配置是否意外跳過了限制
    • 使用 nginx -t 確認配置語法正確
  2. 合法用戶被限制

    • 增加 burst 值以容許突發流量
    • 考慮使用 nodelay 參數
    • 檢查是否有多個用戶共享同一 IP(NAT)
  3. 記憶體不足

    • 增加 zone 的大小(如從 10m 改為 20m)
    • 減少需要追蹤的鍵值數量
  4. 錯誤的狀態碼

    • 確認已設定 limit_req_status 429
    • 某些舊版 Nginx 可能不支援此設定

總結

Nginx 速率限制是保護網站和 API 的重要工具。透過本文介紹的配置方法,您可以:

  • 使用 limit_req_zonelimit_req 控制請求頻率
  • 使用 limit_conn_zonelimit_conn 控制同時連線數
  • 透過 burstnodelay 參數處理突發流量
  • 設定白名單為信任的 IP 提供例外
  • 建立完善的日誌和監控機制
  • 根據不同端點需求設定多層限制策略

記住,速率限制需要根據實際流量模式進行調整。建議從較寬鬆的設定開始,持續監控並逐步優化,以達到安全與使用者體驗的最佳平衡。

comments powered by Disqus
Built with Hugo
Theme Stack designed by Jimmy