說明

本篇文章介紹 Nginx 的限制請求速率模組 ngx_http_limit_req_module,說明可用指令和基本範例。限制請求速率通常用來防止 DDOS 或是暴力破解密碼等惡意攻擊,該模組有以下指令:

  • limit_req_zone
  • limit_req
  • limit_req_dry_run
  • limit_req_log_level
  • limit_req_status

指令

limit_req_zone

標題內容
語法limit_req_zone key zone=name:size rate=rate [sync];
預設值-
Contexthttp

這個指令是定義規則,相關的參數如下

  • key:可以視為符合的條件,例如照 IP、Server Name 或國家等等不同條件,參考最後的範例。
  • name:唯一名稱。
  • size:配置多少記憶體。
  • rate:限制的速率,可以是每秒或是每分多少次。
  • sync:是商業版的功能,暫時先不討論。

簡單的例子

1
limit_req_zone $binary_remote_addr zone=perip:10m rate=1r/s;

limit_req

標題內容
語法limit_req zone=name [burst=number] [nodelay | delay=number];
預設值-
Contexthttp, server, location

設定限制的行為,相關的參數如下

  • zone:limit_req_zone 自訂的名稱
  • burst:選填,超出限制時,提供多少的 Queue 給受限的 Request。
  • delay:選填,可以設定 nodelay 或是 delay 一個數字,看下面的詳細說明。

我們後面都使用每秒一個請求的速率限制,然後假設每秒固定有 5 個請求的情境,分別對不同的設定做說明:

1
limit_req_zone $binary_remote_addr zone=perip:10m rate=1r/s;

預設行為

如果都不使用 burst 和 delay

1
limit_req zone=perip;
預設行為

這是最簡單的邏輯,每秒就是只能一個請求,超過都會被擋掉。

burst

如果都只使用 burst

1
limit_req zone=perip burst=3;
burst

burst 設定為 3,相當於有 3 個 queue 可以存放超過的請求,等到時間過去,有新的額度可用時處理。而在 Queue 滿的情況下,新的請求都會擋掉。上圖每秒處理一個請求,所以 Queue 每秒也會釋出一個空間讓新的請求進到 Queue 中。

nodelay

如果使用 burst 和 nodelay

1
limit_req zone=perip burst=3 nodelay;
nodelay

上面只使用 burst 的時候,要等新的額度才處理,實務上可能會延遲很久不好用。使用 nodelay 就不用等新的額度,當下可以馬上處理,像是預支未來的額度。

delay

如果使用 burst 和 delay

1
limit_req zone=perip burst=3 delay=2;
delay

使用 delay 則是上面兩種模式的混合。這邊設定的 delay 為 2,意思是允許 2 個請求不延遲直接處理,超過的則會延遲。所以其實 delay=2 這個命名不好,應該叫 nodelay=2 才比較符合他的意思。而如果 delay 數字設定和 burst 數字一樣的話,就相當於 nodelay,設定 0 的話則會 error。

這個例子可以想像成有三格能量,前兩個是不延遲的,第三個是延遲的。當能量用完後,等待每秒恢復能量,但他會從後面延遲的開始補充,所以上圖會看到後面新進的請求都會延遲。如果上面例子的第二秒沒有請求進來,可以恢復兩格能量的話,第三秒則有一個請求可以是無延遲的。如下圖:
delay

limit_req_dry_run

標題內容
語法limit_req_dry_run on | off;
預設值off
Contexthttp, server, location

測試模式,可以在 log 中看到規則生效的訊息,但實際上不會真的擋掉請求。Log 大概如下:

1
2024/06/22 14:32:43 [error] 22299#22299: *1623789 limiting requests, dry run, excess: 5.900 by zone "perip", client: 123.123.123.123, server: example.com, request: "GET /mypath HTTP/1.1", host: "example.com", referrer: "https://example.com/"

會看到有 dry run 的字樣。

limit_req_log_level

標題內容
語法limit_req_log_level info | notice | warn | error;
預設值error
Contexthttp, server, location

設定限制請求速率相關 Log 的等級,相關 log 都會輸出到 error.log 中。由於預設 error.log 只記錄 error 等級,所以這邊如果改成 warn 的話預設會看不到。要搭配修改 error_log,例如:

1
2
3
error_log /var/log/nginx/error.log warn;

limit_req_log_level warn;

就會看到相關 log 變成 warn

1
2024/06/20 14:44:32 [warn] 22299#22299: *1623789 limiting requests, ...

limit_req_status

標題內容
語法limit_req_status code
預設值503
Contexthttp, server, location

設定限制連線發生時回應的 HTTP Status Code。只能設定 400 - 599 之間。否則會看到下面錯誤:

1
nginx: [emerg] value must be between 400 and 599 in /etc/nginx/sites-enabled/example:16

範例

限制 IP

每個 IP 每秒一個請求

1
2
3
4
5
limit_req_zone $binary_remote_addr zone=perip:10m rate=1r/s;

server {
limit_req zone=perip burst=5 nodelay;
}

限制 Server Name

每個 Server name 每秒 10 個請求

1
2
3
4
5
limit_req_zone $server_name zone=perserver:10m rate=10r/s;

server {
limit_req zone=perserver burst=10;
}

特定 Path

每個 IP 每秒一個請求

1
2
3
4
5
6
7
server {
limit_req_zone $binary_remote_addr zone=perip:10m rate=1r/s;

location /login/ {
limit_req zone=one burst=5;
}
}

多重條件

可同時多組

1
2
3
4
server {
limit_req zone=perip burst=5 nodelay;
limit_req zone=perserver burst=10;
}

白名單 / 黑名單

limit_req_zone 可以用變數,下面的寫法為指定 IP 為白名單,不限制請求速率:

1
2
3
4
5
6
7
8
9
10
map $remote_addr $not_whitelist {
default $binary_remote_addr;
123.123.123.123 '';
}

limit_req_zone $not_whitelist zone=not_whitelist:10m rate=1r/s;

server {
limit_req zone=not_whitelist burst=5 nodelay;
}

利用這個機制可以做出不同 IP 不同限制:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
map $remote_addr $not_whitelist {
default $binary_remote_addr;
123.123.123.123 '';
}
map $remote_addr $whitelist {
default '';
123.123.123.123 $binary_remote_addr;
}

limit_req_zone $not_whitelist zone=not_whitelist:10m rate=1r/s;
limit_req_zone $whitelist zone=whitelist:10m rate=10r/s;;

server {
limit_req zone=not_whitelist burst=5 nodelay;
limit_req zone=whitelist burst=10 nodelay;
}

反過來用也可以作為黑名單使用。

設定建議

這是官方部落格的設定範例

1
2
3
4
5
limit_req_zone $binary_remote_addr zone=perip:10m rate=5r/s;

server {
limit_req zone=perip burst=12 delay=8;
}

考慮多開分頁和共用 IP 以及自己實際情況的情況,可再進行調整。另外,可以在登入之類,可能被嘗試暴力破解的地方,做更嚴格的限制。

延伸閱讀

Nginx 限制連線數 (ngx_http_limit_conn_module)