Nginx 限制請求速率 (ngx_http_limit_req_module)
說明
本篇文章介紹 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]; |
預設值 | - |
Context | http |
這個指令是定義規則,相關的參數如下
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]; |
預設值 | - |
Context | http , 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 設定為 3,相當於有 3 個 queue 可以存放超過的請求,等到時間過去,有新的額度可用時處理。而在 Queue 滿的情況下,新的請求都會擋掉。上圖每秒處理一個請求,所以 Queue 每秒也會釋出一個空間讓新的請求進到 Queue 中。
nodelay
如果使用 burst 和 nodelay
1 | limit_req zone=perip burst=3 nodelay; |
上面只使用 burst 的時候,要等新的額度才處理,實務上可能會延遲很久不好用。使用 nodelay 就不用等新的額度,當下可以馬上處理,像是預支未來的額度。
delay
如果使用 burst 和 delay
1 | limit_req zone=perip burst=3 delay=2; |
使用 delay 則是上面兩種模式的混合。這邊設定的 delay 為 2,意思是允許 2 個請求不延遲直接處理,超過的則會延遲。所以其實 delay=2 這個命名不好,應該叫 nodelay=2 才比較符合他的意思。而如果 delay 數字設定和 burst 數字一樣的話,就相當於 nodelay,設定 0 的話則會 error。
這個例子可以想像成有三格能量,前兩個是不延遲的,第三個是延遲的。當能量用完後,等待每秒恢復能量,但他會從後面延遲的開始補充,所以上圖會看到後面新進的請求都會延遲。如果上面例子的第二秒沒有請求進來,可以恢復兩格能量的話,第三秒則有一個請求可以是無延遲的。如下圖:
limit_req_dry_run
標題 | 內容 |
---|---|
語法 | limit_req_dry_run on | off; |
預設值 | off |
Context | http , 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 |
Context | http , server , location |
設定限制請求速率相關 Log 的等級,相關 log 都會輸出到 error.log 中。由於預設 error.log 只記錄 error 等級,所以這邊如果改成 warn 的話預設會看不到。要搭配修改 error_log,例如:
1 | error_log /var/log/nginx/error.log warn; |
就會看到相關 log 變成 warn
1 | 2024/06/20 14:44:32 [warn] 22299#22299: *1623789 limiting requests, ... |
limit_req_status
標題 | 內容 |
---|---|
語法 | limit_req_status code |
預設值 | 503 |
Context | http , 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 | limit_req_zone $binary_remote_addr zone=perip:10m rate=1r/s; |
限制 Server Name
每個 Server name 每秒 10 個請求
1 | limit_req_zone $server_name zone=perserver:10m rate=10r/s; |
特定 Path
每個 IP 每秒一個請求
1 | server { |
多重條件
可同時多組
1 | server { |
白名單 / 黑名單
limit_req_zone
可以用變數,下面的寫法為指定 IP 為白名單,不限制請求速率:
1 | map $remote_addr $not_whitelist { |
利用這個機制可以做出不同 IP 不同限制:
1 | map $remote_addr $not_whitelist { |
反過來用也可以作為黑名單使用。
設定建議
這是官方部落格的設定範例
1 | limit_req_zone $binary_remote_addr zone=perip:10m rate=5r/s; |
考慮多開分頁和共用 IP 以及自己實際情況的情況,可再進行調整。另外,可以在登入之類,可能被嘗試暴力破解的地方,做更嚴格的限制。