> For the complete documentation index, see [llms.txt](https://tritueviet.gitbook.io/server/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://tritueviet.gitbook.io/server/nginx.md).

# Nginx

Một trong những tính năng rất hữu ích nhưng lại hay bị hiểu nhầm hoặc cấu hình sai trong nginx là `rate limiting` - giới hạn tốc độc truy cập. Nó cho phép giới hạn lượng truy vấn HTTP đến máy chủ mà người dùng có thể tạo ra trong một khoảng thời gian. Truy cập ở đây có thể hiểu là những truy cập GET bình thường hoặc POST ở các form HTML.

Giới hạn tốc độ truy cập có thể dùng trong mục đích an ninh, ví dụ ngăn chặn người dùng dò mật khẩu, cứ hình dung mình chỉ cho truy cập 5 lần 1s thì dò đến mai. Nó cũng có tác dụng trong việc ngăn chặn DDoS bằng cách giới hạn tốc độ đến mức của một người dùng thường (ví dụ đo đạc thời điểm bình thường 1 IP chỉ truy cập tối đa là 10 lần 1s, vậy mình đặt giới hạn trên mức này một chút) và xác địch địa chỉ URL bị tấn công. Túm lại là giới hạn sẽ giúp máy chủ không bị quá tải mới quá nhiều truy cập đến từ một người dùng, dành đất cho những người dùng khác.

## Cái này nó làm việc thế nào? <a href="#cai-nay-no-lam-viec-the-nao" id="cai-nay-no-lam-viec-the-nao"></a>

Nó dựa theo thuật toán cái xô thủng. Đại khái là có một cái xô, đục nhiều lỗ để nước có thể chảy qua. Nếu nước được rót từ trên xuống quá nhanh, không chảy kịp thì nó sẽ bị tràn.Cái xô này là hàng đợi chờ xử lý, nước rò ra khỏi xô là các truy vấn được xử lý, còn phần tràn ra là phần bị bỏ đi, không được phục vụ.\
Moá, nghe thế này nông dân cũng làm được :D

## Cấu hình cơ bản giới hạn tốc độ trong nginx <a href="#cau-hinh-co-ban-gioi-han-toc-do-trong-nginx" id="cau-hinh-co-ban-gioi-han-toc-do-trong-nginx"></a>

Giới hạn tốc độ được cấu hình với 2 chỉ lệnh `limit_req_zone` và `limit_req`, ví dụ:

```
limit_req_zone $binary_remote_addr zone=mylimit:10m rate=10r/s;


server {
    location /login/ {
        limit_req zone=mylimit;

        proxy_pass http://my_upstream;
    }
}
```

`limit_req_zone` định nghĩa các tham số cho việc giới hạn tốc độ. Trong khi đó `limit_req` chỉ định chỗ nào sẽ được áp dụng giới hạn. Ở ví dụ trên là `/login/`. `limit_req_zone` được khai báo trong khối `http`, nên nó có thể dùng được ở nhiều phạm vi khác nhau. Nó có 3 tham số:

* Key - Định nghĩa thuộc tính của truy cập mà sẽ được áp dụng giới hạn. Ví dụ `$binary_remote_addr` đại diện cho IP của người dùng. Nó có nghĩa là mới mỗi IP của người dùng sẽ bị giới hạn theo định nghĩa ở tham số thứ 3. Cái này thì cũng giống `$remote_addr` nhưng nó ở dạng nhị phân nên tốn ít không gian hơn.
* Zone - Định nghĩa một vùng bộ nhớ chia sẻ để lưu trạng thái của mỗi địa chỉ IP và số lần nó truy cập đến địa chỉ URL bị giới hạn. Do nó được lưu ở vùng nhớ chung nên có thể được dùng bởi các tiến trình nginx. Cái này chia thành 2 phần, tên của vùng nhớ xác định bằng từ khoá `zone=`, và dung lượng được định nghĩa sau dấu `:`. Bình thường khoảng 16000 địa chỉ IP sẽ tốn khoảng 1MB, như vậy với tham số trong ví dụ, chúng ta có thể lưu được trạng thái của 160000 địa chỉ IP.\
  Nếu vùng nhớ này bị đầy, mà nginx vẫn muốn thêm thông tin của các địa chỉ IP mới, IP cũ nhất sẽ bị xoá đi. Nếu không gian được giải phóng vẫn không đủ để tiếp nhận các bản ghi mới, nginx sẽ trả về mã lỗi `503 (Service Temporarily Unavailable)`. Để phòng tránh việc này, mỗi lần nginx thêm mục mới, nó sẽ thực hiện xoá 2 mục không dùng tới trong vòng 60s.
* Rate - Đặt tốc độ truy cật lớn nhất, như ở ví dụ, tốc độ lớn nhất không vượt quá 10 truy cập 1s. Nginx thực tế theo dõi truy cập theo ms, giới hạn trên thực tế là 1 truy cập trong vòng 100ms. Truy cập sẽ bị loại bỏ nếu xuất hiện trong vòng 100ms từ lần truy cập trước.

Chỉ lệnh `limit_req_zone` đặt tham số cho việc giới hạn tốc độ và định nghĩa vùng nhớ chung, nhưng nó không thực hiện việc giới hạn. Chúng ta cần áp dụng nó vào một `location` cụ thể hoặc khối `server` bằng việc thêm chỉ lệnh `limit_req`. Như trong ví dụ chúng ta giới hạn tốc độ truy cập đến `/login/`.

Như vậy với mỗi địa chỉ IP, chúng ta có chỉ có thể truy cập nhiều nhất 10 lần 1s đến `/login/`, hoặc chúng ta không thể truy cập tới URL này trong vòng 100ms sau lần truy cập trước.

## Xử lý bùng nổ (éo biết dịch thế nào) <a href="#xu-ly-bung-no-eo-biet-dich-the-nao" id="xu-ly-bung-no-eo-biet-dich-the-nao"></a>

Đại khái là điều gì xảy ra với cái truy cập bị loại bỏ? Ví dụ trong vòng 100ms chúng ta gửi 2 truy cập, thì truy cập thứ 2 sẽ trả về `503` cho người dùng. Cái này có vẻ không hay lắm, vì người dùng sẽ thấy ứng dụng rất là lởm. Thay vì đó chúng ta muốn lưu lại các truy cập này để xử lý chúng trong thời gian thích hợp, kiểu cho vào hàng đợi chứ không từ chối vội. Đây là lúc chúng ta sử dụng tham số `burst` trong `limit_req`, cập nhật cấu hình theo như sau:

```
location /login/ {
    limit_req zone=mylimit burst=20;
    proxy_pass http://my_upstream;
}
```

Tham số `burst` định nghĩa số lượng truy cập người dùng có thể vượt so với giới hạn đã định. Theo ví dụ thì truy cập đến trong vòng 100ms sau truy cập trước đó sẽ được đưa vào hàng đợi, và chúng ta đang đặt cỡ của hàng đợi là 20 cái.\
Nó có nghĩa là nếu chúng ta gửi 21 truy cập đến địa chỉ `/login/` trong vòng 100ms thì cái đầu tiên sẽ được xử lý ngay lập tức, 20 cái còn lại sẽ được gửi vào hàng đợi. Sau đó nó sẽ xử lý hàng đợi mỗi 100ms, mã lỗi `503` chỉ được trả về nếu truy cập đến quá nhanh và hàng đợi vượt quá 20.

## Hàng đợi với No Delay (Jétstar airline à :D) <a href="#hang-doi-voi-no-delay-jetstar-airline-a-d" id="hang-doi-voi-no-delay-jetstar-airline-a-d"></a>

Một cấu hình với `burst` mong đợi một luồng truy cập mượt mà, không bị trả về lỗi nếu vượt quá giới hạn một chút, nhưng không thực tế lắm vì nó sẽ làm trang web của bạn có vẻ chậm đi. Trong ví dụ ở trên, truy cập thứ 20 trong hàng đợi sẽ phải chờ 20 \* 100ms = 2s mới đến lượt, điều này nhiều khi trở nên vô ích với người dùng. Để xử lý tình huống này, thêm tham số `nodelay` cùng với tham số `burst`:

```
location /login/ {
    limit_req zone=mylimit burst=20 nodelay;
    proxy_pass http://my_upstream;
}
```

Với tham số `nodelay`, NGINX vẫn phân bổ các vị trí trong hàng đợi theo tham số `burst` và áp đặt giới hạn tốc độ đã được cấu hình, nhưng nó không làm ảnh hướng đến phần xử lý những truy cập trong hàng đợi. Thay vì đó nếu một truy cập đến quá sớm, NGINX chuyển nó đi ngay lập tức như là có một vị trí của nó trong hàng đợi. Nó đánh dấu vị trí này đã bị lấy trong hàng đợi và giữ nó đến khi khoảng thời gian giới hạn trôi qua (trong ví dụ này là 100ms).

Như lúc trước chúng ta giả sử có 20 vị trí trong hàng đợi và có 21 truy cập đồng thời đến từ một địa chỉ IP. NGINX chuyển tất cả 21 truy cập này ngay lập tức và đánh dấu 20 vị trí trong hàng đợi là “đã bị lấy”, sau đó làm trống mỗi vị trí sau 100ms. Nếu có 25 truy cập tương tự đến cùng lúc, NGINX sẽ xử lý ngay lập tức 21 truy cập, đánh dấu 20 vị trí trong hàng đợi là đã bị lấy, và từ chối 4 truy cập còn lại với trạng thái 503.

Giả sử sau 101ms sau khi lượng truy cập đầu tiên đổ vào, 20 truy cập đồng thời tiếp tục đến. Chỉ có 1 vị trí trong hàng đợi được làm trống, vì vậy NGINX xử lý 1 truy cập và từ chối 19 truy cập còn lại với trạng thái 503. Vào khoảng thời gian 501ms sau đợt truy cập vừa xong, 20 lượt truy cập đồng thời mới lại tới, có 5 vị trí trống nên NGINX sẽ xử lý 5 truy cập mới và từ chối 15 cái còn lại.

Hiệu ứng này tương tự với việc giới hạn 10 truy cập trong 1s. Tham số `nodelay` hữu ích khi chúng ta muốn áp đặt giới hạn truy cập mà không muốn bị chờ giữa các truy cập.

Tham số `burst` và `nodelay` được khuyến khích dùng trong quá trình triển khai giới hạn truy cập.

## Các ví dụ nâng cao <a href="#cac-vi-du-nang-cao" id="cac-vi-du-nang-cao"></a>

Kết hợp giới hạn tốc độ truy cập với các tính năng khác của NGINX chúng ta có thể tạo ra nhiều kiểu giới hạn truy cập khác nhau.

### Whitelisting <a href="#whitelisting" id="whitelisting"></a>

Ví dụ bên dưới mô tả cách giới hạn tốc độ truy cập từ bất kì ai, trừ những người trong *whitelist*

```
geo $limit {
    default 1;
    10.0.0.0/8 0;
    192.168.0.0/24 0;
}


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

limit_req_zone $limit_key zone=req_zone:10m rate=5r/s;

server {
    location / {
        limit_req zone=req_zone burst=10 nodelay;

        # ...
    }
}
```

Ví dụ trên có sử dụng chỉ lệnh `geo` và `map`. Khối `geo` gán 0 cho các địa chỉ trong `whitelist` và gán 1 cho các địa chỉ IP khác vào biến `$limit`. Sau đó chúng ta dùng `map` để chuyển đổi các giá trị này thành các `$limit_key`:

* Nếu `$limit=0`, `$limit_key` được để trống
* Nếu `$limit=1`, `$limit_key` là địa chỉ IP của người dùng theo định dạng nhị phân\
  Tổng hợp lại thì ta sẽ có `$limit_key` được để trống nếu truy cập đến từ các địa chỉ trong `whitelist` (10.0.0.0/8, 192.168.0.0/24), và `$limit_key` là IP người dùng trong các trường hợp còn lại. Với `$limit_key` để trống trong chỉ lệnh `limit_req_zone`, giới hạn tốc độ sẽ không được áp dụng, vì vậy các địa chỉ IP trong `whitelist` có thể truy cập thoải mái, trong khi đó các địa chỉ IP khác sẽ bị giới hạn 5 truy cập trong 1s.\
  Chỉ lệnh `limit_req` ở trên áp dụng ở vị trí `/` và cho phép vượt qua thới hạn đã đặt 10 truy cập với tham số `nodelay` đã nói ở trên.

### Sử dụng nhiều chỉ lệnh `limit_req` trong một vị trí <a href="#su-dung-nhieu-chi-lenh-limit-req-trong-mot-vi-tri" id="su-dung-nhieu-chi-lenh-limit-req-trong-mot-vi-tri"></a>

Chúng ta có thể sử dụng nhiều chỉ lệnh `limit_req` trong một vị trí. Tất cả giới hạn phụ hợp với truy cập đều được áp dụng, điều này có nghĩa là giới hạn nào chặt hơn thì được dùng. Ví dụ, nếu nhiều chỉ lệnh với độ trễ được đưa ra thì độ trễ dài nhất được áp dụng. Để cho dễ hình dung thì truy cập bị từ chối nếu nó bị vào trong bất cứ chỉ lệnh `limit_req` nào, ví dụ có 3 giới hạn, chỉ cần 1 giới hạn đạt tới là truy cập bị từ chối, mặc dù 2 giới hạn còn lại cho phép nó chạy qua.

Mở rộng ví dụ trên chúng ta có thể đặt một giới hạn tốc độ truy cập riêng cho các IP tron `whitelist`:

```
http {
    # ...


    limit_req_zone $limit_key zone=req_zone:10m rate=5r/s;
    limit_req_zone $binary_remote_addr zone=req_zone_wl:10m rate=15r/s;

    server {
        # ...
        location / {
            limit_req zone=req_zone burst=10 nodelay;
            limit_req zone=req_zone_wl burst=20 nodelay;
            # ...
        }
    }
}
```

Các địa chỉ IP trong `whitelist` không khớp với giới hạn đầu tiên (`req_zone`) nhưng lại khớp với giới hạn bên dưới (`req_zone_wl`) nên bị giới hạn 15 truy cập 1s. Các IP không trong `whitelist` khớp với cả 2 giới hạn nên giới hạn chặt hơn sẽ được sử dụng, ở đây là `req_zone` với 5 truy cập 1s.

### Cấu hình các thành phần liên quan <a href="#cau-hinh-cac-thanh-phan-lien-quan" id="cau-hinh-cac-thanh-phan-lien-quan"></a>

#### Logging <a href="#logging" id="logging"></a>

Mặc định NGINX ghi lại các truy cập bị trễ hoặc từ chối, kiểu:

```
2015/06/13 04:20:00 [error] 120315#0: *32086 limiting requests, excess: 1.000 by zone "mylimit", client: 192.168.1.2, server: nginx.com, request: "GET / HTTP/1.0", host: "nginx.com"
```

Các trường bao gồm:

* *limiting requests* - Chỉ ra rằng phần này ghi lại một truy cập bị giới hạn
* *excess* - Số request mỗi ms theo như tốc độ giới hạn được cấu hình
* *client* - Địa chỉ IP của người dùng
* *server* - Địa chỉ IP hoặc hostname của máy chủ
* *request* - Truy cập HTTP đến từ người dùng
* *host* - Giá trị của biến `host` trong HTTP Header

Mặc định, NGINX ghi lại những truy cập bị từ chối ở mức độ `error` như ở ví dụ trên. Để thay đổi logging level, sử dụng chỉ lệnh `limit_req_log_level`. Chúng ta đặt chế độ log ở mức độ `warn` như sau:

```
location /login/ {
    limit_req zone=mylimit burst=20 nodelay;
    limit_req_log_level warn;


    proxy_pass http://my_upstream;
}
```

#### Mã lỗi trả về cho người dùng <a href="#ma-loi-tra-ve-cho-nguoi-dung" id="ma-loi-tra-ve-cho-nguoi-dung"></a>

Mặc định, NGINX trả về mã trạng thái `503 (Service Temporarily Unavailable)` khi người dùng truy cập quá tốc độ cho phép. Sử dụng chỉ lệnh `limit_req_status` để đặt mã trạng thái khác nếu muốn. Ví dụ chúng ta muốn chuyển sang mã `444`:

```
location /login/ {
    limit_req zone=mylimit burst=20 nodelay;
    limit_req_status 444;
}
```

#### Từ chối mọi truy cập tại một vị trí <a href="#tu-choi-moi-truy-cap-tai-mot-vi-tri" id="tu-choi-moi-truy-cap-tai-mot-vi-tri"></a>

Nếu muốn chặn truy cập đến một vị trí nào đó, thay vì sử dụng giới hạn tốc độ, chúng ta dùng khối `location` để tạo một vị trí và thêm chỉ lệnh `deny all`:

```
location /foo.php {
    deny all;
}
```

## Tổng kết <a href="#tong-ket" id="tong-ket"></a>

Túm lại là với mấy cái này mình đã biết cách giới hạn tốc độ truy cập của người dùng. Chúng ta có thể đặt nhiều giới hạn cho từng vị trí khác nhau. Ngoài ra chúng ta có thêm 2 tham số hữu ích là `burst` và `nodelay`. Chúng ta cũng có thể đặt `whitelist` và `blacklist` cho từng tập địa chỉ IP sau đó áp dụng các giới hạn khác nhau. Ngoài ra là mấy cái lặt vặt như log, đổi mã trạng thái,…

{% embed url="<https://sbin.xyz/2018/01/30/Nginx-gioi-han-toc-do-truy-cap-cua-nguoi-dung/>" %}

{% embed url="<https://www.nginx.com/blog/rate-limiting-nginx/>" %}


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://tritueviet.gitbook.io/server/nginx.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
