Giới hạn Rate Limit trên Nginx: Tuyệt chiêu chặn đứng tấn công DDoS Layer 7 cho VPS

Tác giả: Trần Thảo 09 tháng 05, 2026

Nửa đêm, hệ thống cảnh báo réo ầm ĩ. Bạn hốt hoảng nhìn dashboard monitor: CPU của VPS chạm đỉnh 100%, RAM cạn kiệt, log Nginx trôi như thác đổ. Đây là dấu hiệu điển hình của một đợt tấn công tầng ứng dụng. Trước khi đi sâu vào kỹ thuật chống spam, hãy đảm bảo bạn đã thực hiện các bước bảo mật VPS Linux toàn diện để tạo nền tảng vững chắc nhất cho máy chủ.

Việc thiết lập cấu hình Rate Limit trên Nginx chính là chốt chặn giúp bạn ngăn chặn các đợt thu thập dữ liệu (scraping), chặn đứng spam request và duy trì hoạt động cho backend.

Vậy làm thế nào để setup vũ khí này hoạt động hiệu quả nhất dựa trên tài liệu chuẩn kỹ thuật, mà không vô tình chặn nhầm traffic của người dùng thật? Những sai lầm chí mạng nào khiến Sysadmin lầm tưởng mình đã an toàn cho đến khi server thực sự sụp đổ? Hãy cùng đi sâu vào từng dòng code thực chiến ngay sau đây.

Infographic tổng quan Nginx Rate Limit chặn đứng tấn công DDoS Layer 7, bảo vệ VPS khỏi quá tải request.

Nginx Rate Limit hoạt động như một tấm khiên vững chắc ở tầng ứng dụng, lọc bỏ request không hợp lệ và chỉ cho phép traffic thực của người dùng đi qua.

Bản chất DDoS Layer 7 và lý do tường lửa (Firewall) bất lực

Trước khi gõ lệnh, anh em developer cần hiểu rõ bản chất kẻ thù mà hạ tầng đang phải đối mặt. Tấn công DDoS Layer 7 nhắm trực tiếp vào tầng ứng dụng (Application Layer). Các Firewall hay giải pháp chống DDoS thông thường ở tầng mạng (Layer 3/4) thường bất lực và khiến VPS nhanh chóng sụp đổ vì những lý do sau:

1. Vượt qua tường lửa nhờ ngụy trang tinh vi

Các tường lửa truyền thống thường phát hiện tấn công bằng cách nhận diện các luồng dữ liệu khổng lồ (volumetric). Tuy nhiên, tấn công Layer 7 lại tiêu thụ lượng băng thông cực kỳ nhỏ và các gói tin HTTP trông hoàn toàn hợp lệ về mặt giao thức. Tường lửa không có khả năng đọc hiểu logic ứng dụng để phân biệt đâu là HTTP request từ người thật, đâu là từ botnet.

2. Vắt kiệt VPS bằng phương pháp Low and Slow

Tấn công không nhất thiết phải ồ ạt. Các công cụ như Slowloris lợi dụng hành vi của giao thức HTTP để gửi các phần header không hoàn chỉnh một cách cực kỳ chậm rãi. Máy chủ web buộc phải giữ mở các worker connection để chờ đợi. Khi giới hạn kết nối đồng thời (maximum concurrent connection pool) bị lấp đầy, người dùng thật sẽ bị từ chối phục vụ.

3. Đánh sập Backend và Database

Dù Firewall có thể chịu tải tốt ở điểm đầu vào, nhưng khi các request HTTP lọt vào đến máy chủ, chúng ép VPS phải xử lý các tác vụ tốn kém. Một đợt spam request POST vào tính năng tìm kiếm (search) có thể kích hoạt hàng loạt truy vấn SQL phức tạp, vắt kiệt CPU/RAM của Database và gây crash toàn bộ hệ thống.

Ngoài việc giới hạn request, bạn có thể tham khảo cách triển khai mô hình Zero Trust trên VPS để kiểm soát truy cập ở mức độ tinh vi hơn, chặn đứng các nguy cơ từ bên trong lẫn bên ngoài.

Giải mã thuật toán Leaky Bucket (xô rò rỉ) trong Nginx

Nginx (cụ thể là module ngx_http_limit_req_module) kiểm soát lưu lượng và làm phẳng các đợt bùng nổ traffic dựa trên thuật toán kinh điển mang tên Leaky Bucket. Cơ chế này hoạt động dựa trên một phép ẩn dụ cực kỳ trực quan:

  • Nước (Water) = Client Requests: Các request từ người dùng đổ vào server với tốc độ không đồng đều (lúc nhỏ giọt, lúc ồ ạt).
  • Cái xô (Bucket) = Hàng đợi (Queue): Cái xô có dung tích nhất định. Nếu request đến quá nhanh mà server chưa kịp xử lý, chúng được xếp vào hàng đợi (FIFO) thay vì bị vứt bỏ ngay.
  • Lỗ rò rỉ (Leak) = Tốc độ xử lý (Rate): Đáy xô có một lỗ rò rỉ khiến nước chảy ra với tốc độ cố định và không đổi. Nginx sẽ tuần tự lấy request từ hàng đợi ra xử lý theo đúng tần số giới hạn đã lập trình (ví dụ: 10 request/giây).
  • Tràn xô (Overflow) = Loại bỏ request: Nếu lượng request ập tới làm lấp đầy hàng đợi, phần nước tràn ra ngoài sẽ bị Nginx từ chối ngay lập tức (trả về lỗi 503 hoặc 429).
Sơ đồ infographic giải thích cơ chế hoạt động của thuật toán Leaky Bucket trong Nginx Rate Limit.

Sơ đồ mô phỏng thuật toán Leaky Bucket: request đổ vào không đều nhưng được Nginx rỏ giọt xuống backend với tốc độ cố định.

Hướng dẫn cấu hình Rate Limit trên Nginx thực chiến

Quy trình thiết lập Rate Limit bao gồm 2 bước: Khai báo không gian nhớ ở block http và áp dụng giới hạn ở block server hoặc location.

Bước 1: Khai báo không gian nhớ (limit_req_zone) và bài toán RAM 64-bit

Mở file nginx.conf và thêm cấu hình sau:

http {
    # Khai báo zone giới hạn request
    limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
}

Phân tích kỹ thuật chuyên sâu:

  • $binary_remote_addr: Đây là biện pháp chống cạn kiệt bộ nhớ (OOM). Thay vì dùng $remote_addr lưu IP dưới dạng chuỗi văn bản (tốn từ 7 đến 15 byte), biến nhị phân này luôn cố định ở mức 4 byte cho IPv4 và 16 byte cho IPv6.
  • Sức chứa thực tế của Zone 10MB: Rất nhiều tài liệu cũ nói 1MB lưu được 16.000 IP. Nhưng theo tài liệu Nginx, trên nền tảng máy chủ 64-bit hiện đại, mỗi trạng thái (state) của module limit_req tiêu tốn 128 byte (trong khi limit_conn tiêu tốn 64 byte).
  • Bởi vì Nginx cần trích ra một phần không gian để duy trì cấu trúc quản lý chung (như slab allocator và cây rbtree), nên 1MB RAM sẽ lưu được khoảng (about) 8.000 IP. Khi bạn thiết lập zone=api_limit:10m (10MB), Nginx có đủ không gian để theo dõi trạng thái của khoảng 80.000 IP hoạt động đồng thời.

Bước 2: Xử lý traffic bùng nổ (bursty) bằng burst và nodelay

Lưu lượng Web thực tế không bao giờ đều đặn. Khi user tải trang, trình duyệt thường bắn 10-20 request đồng thời để kéo HTML, CSS, JS và gọi API. Nếu cấu hình cứng nhắc, Nginx sẽ block mất các request hợp lệ này.

server {
    location /api/v1/ {
        limit_req zone=api_limit burst=20 nodelay;
        proxy_pass http://backend_upstream;
    }
}
  • burst=20: Xác định dung lượng của cái xô. Cho phép tối đa 20 request vượt mức được xếp vào hàng đợi chờ xử lý.
  • Tham số nodelay (Yếu tố quyết định UX): Nếu chỉ dùng burst, Nginx sẽ ép các request trong hàng đợi phải xử lý chậm rãi từng cái một (gây ra độ trễ giả tạo rất lớn cho user thật). Khi thêm nodelay, Nginx sẽ xử lý ngay lập tức toàn bộ request đang nằm trong sức chứa burst. Tuy nhiên, Nginx vẫn đánh dấu các vị trí (slot) trong xô là đã sử dụng và chỉ giải phóng dần theo tốc độ rate=10r/s. Cấu hình này giúp cân bằng hoàn hảo giữa trải nghiệm lướt web tốc độ cao và bảo mật chống lạm dụng.
Infographic so sánh trải nghiệm người dùng giữa cấu hình Nginx chỉ dùng burst và burst kết hợp nodelay.

Sự khác biệt về latency: burst cứng nhắc làm chậm trang web, trong khi nodelay cho phép xử lý bùng nổ traffic ngay lập tức nhưng vẫn khóa slot trong zone để bảo mật.

Xem thêm cách tối ưu VPS WordPress chịu tải cực đại để phối hợp nhịp nhàng giữa cache và giới hạn tốc độ.

Sai lầm ngây thơ: Dùng limit_conn để chống Slowloris

Rất nhiều tutorial hướng dẫn dùng limit_conn (giới hạn kết nối) để chống lại tấn công Slowloris. Đây là một lầm tưởng chết người!

Theo tài liệu của module ngx_http_limit_conn_module: “Một kết nối chỉ được đếm khi máy chủ đang xử lý một request và toàn bộ phần HTTP header đã được đọc xong.

Bản chất của Slowloris là liên tục gửi các HTTP request không hoàn chỉnh, cố tình ngâm kết nối và không bao giờ gửi dòng cuối cùng của header. Do đó, module limit_conn hoàn toàn không nhận diện được và không đưa các kết nối độc hại này vào bộ đếm.

Vũ khí thực sự là Timeout: Để khắc chế Slowloris, khắc tinh chính là các chỉ thị giới hạn thời gian giúp dọn dẹp các kết nối lỗi.

http {
    # 1. Khắc tinh của Slowloris (Cắt đứt kết nối ngâm header/body)
    client_header_timeout 10s;
    client_body_timeout   10s;
    send_timeout          10s; # Chống Slow Read
    
    # 2. Vũ khí bọc lót (Giới hạn số kết nối ĐÃ QUA CỬA HEADER)
    limit_conn_zone $binary_remote_addr zone=conn_limit_per_ip:10m;
    
    server {
        limit_conn conn_limit_per_ip 15;
    }
}

Nếu bạn đang chạy cụm server, hãy cân nhắc việc cấu hình HAProxy Load Balancer phía trước Nginx để lọc bớt các kết nối không hợp lệ này trước khi chúng chạm tới web server chính.

Kiến trúc phòng thủ nhiều lớp (CDN, Whitelist, Fail2Ban)

Sơ đồ kiến trúc infographic mô tả sự phối hợp phòng thủ nhiều lớp giữa CDN, Fail2ban ở tầng mạng và Nginx ở tầng ứng dụng.

Mô hình phòng thủ chiều sâu: Mỗi lớp chặn một loại tấn công khác nhau, tổng thể tạo nên một pháo đài bảo vệ VPS vững chắc nhất.

Bóc tách Real IP khi đứng sau CDN (Cloudflare)

Nếu hệ thống của bạn nằm sau Cloudflare hoặc Load Balancer, Nginx sẽ lấy nhầm IP của CDN để đếm Rate Limit, dẫn đến việc khóa lây toàn bộ người dùng hợp lệ. Bạn bắt buộc phải bóc tách IP gốc bằng ngx_http_realip_module.

http {
    # Khai báo dải IPv4 và IPv6 của Cloudflare (Cập nhật từ cloudflare.com/ips)
    set_real_ip_from 103.21.244.0/22;
    set_real_ip_from 103.22.200.0/22;
    # ...thêm đầy đủ các dải IP khác...

    # Yêu cầu Nginx lấy IP từ Header CF-Connecting-IP
    real_ip_header CF-Connecting-IP;
    real_ip_recursive on; # Rất được khuyến khích để lọc chuỗi proxy

    limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
}

Miễn trừ (Whitelist) IP nội bộ bằng geo và map

Để không chặn nhầm IP văn phòng hay hệ thống monitoring, bạn sử dụng geomap. Theo cơ chế của Nginx, nếu biến key truyền vào limit_req_zone là một chuỗi rỗng (""), request đó sẽ không bị ghi nhận và hoàn toàn không bị giới hạn.

http {
    geo $whitelist_ip {
        default        0; 
        192.168.1.0/24 1; # Dải IP LAN văn phòng
    }

    map $whitelist_ip $limit_key {
        0 $binary_remote_addr; 
        1 ""; # Trả về chuỗi rỗng để Nginx bỏ qua Rate Limit
    }

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

Sau khi cấu hình xong, hãy kiểm tra tốc độ phản hồi của server. Nếu muốn tối ưu hơn nữa, bạn nên tìm hiểu cách bật HTTP/3 trên VPS Nginx để vừa bảo mật, vừa tăng tốc độ tải trang đáng kể.

Ngăn chặn IP vĩnh viễn với Fail2ban ở tầng mạng

Nginx làm tốt việc giới hạn tốc độ ứng dụng, nhưng nếu kẻ tấn công liên tục spam request, CPU vẫn bị hao tốn để trả về mã lỗi. Hãy để Fail2ban theo dõi file error.log của Nginx và cập nhật tường lửa (iptables) để cấm hẳn kết nối từ tầng mạng.

Chỉnh sửa cấu hình Jail (/etc/fail2ban/jail.local):

[nginx-limit-req]
enabled = true
port    = http,https
filter  = nginx-limit-req
logpath = /var/log/nginx/error.log
findtime = 600
maxretry = 10
bantime  = -1  # Cảnh báo: Giá trị -1 sẽ chặn IP vĩnh viễn

(Lưu ý: Thiết lập bantime = -1 mang tính loại bỏ vĩnh viễn giúp chặn IP vĩnh viễn, nhưng hãy cẩn trọng vì nếu cấu hình rate limit quá gắt, bạn có thể tự khóa người dùng hợp lệ mãi mãi).

Tối ưu UX: Trả về mã lỗi 429 và chuẩn hóa Error Page

Khi bị chặn, Nginx mặc định trả về mã lỗi 503 (Service Temporarily Unavailable). Điều này sai về mặt ngữ nghĩa và dễ làm crash các ứng dụng Frontend/Mobile đang mong đợi dữ liệu JSON.

Theo chuẩn quốc tế RFC 6585, mã lỗi chuẩn cho việc vượt quá tốc độ là 429 (Too Many Requests). Mặc dù RFC quy định việc đính kèm header Retry-After chỉ ở mức tùy chọn (MAY), nhưng đứng dưới góc độ thực chiến, developer rất nên đưa vào để Client App hiển thị thanh đếm ngược văn minh.

http {
    limit_req_status 429;
    limit_conn_status 429;
    
    server {
        # Sử dụng Named Location để cấu hình JSON Response
        error_page 429 @rate_limit_response;

        location @rate_limit_response {
            default_type application/json;
            add_header Retry-After 60 always; 
            return 429 '{"error": "Too Many Requests", "message": "Thao tác quá nhanh. Vui lòng chờ 60s."}';
        }
    }
}

(Giải đáp thắc mắc cú pháp: Rất nhiều Sysadmin phân vân về việc có được dùng dấu bằng = trước named location hay không. Theo tài liệu Nginx, cú pháp error_page 404 = @fallback; là hoàn toàn hợp lệ. Nginx sẽ giữ nguyên URI và phản hồi cho client bằng chính mã HTTP mà block named location sinh ra).

Câu hỏi thường gặp (FAQ)

1. Rate limit Nginx tối ưu là bao nhiêu?

Không có con số vàng chung, bạn phải chia theo từng Endpoint:

  • API cực nhạy cảm (Login, OTP, Reset Password): Rất khắt khe. Khoảng 1-5r/m (1 đến 5 request/phút), kết hợp burst=3.
  • API lấy dữ liệu (Search, Feed): Nới lỏng hơn. Khoảng 10-20r/s, kết hợp burst=20 nodelay.
  • Static file (CSS, JS, Image): Không nên rate limit, hoặc để mức cực cao.

2. Tại sao bật Rate Limit lại trả về lỗi 503 (Service Unavailable) thay vì 429?

Vì mặc định của Nginx là trả về 503 khi drop request. Để đổi sang mã 429 (Too Many Requests) chuẩn quốc tế, bạn chỉ cần thêm dòng limit_req_status 429; vào block http hoặc server.

3. Dùng tham số nodelay có khiến server bị sập khi traffic bùng nổ không?

Không. nodelay chỉ giúp Nginx xử lý ngay lập tức các request nằm trong sức chứa hàng đợi (burst) để UX không bị lag. Slot trong hàng đợi vẫn bị trừ đi và hồi lại từ từ. Nếu kẻ tấn công spam dồn dập, hàng đợi đầy thì request sau vẫn lập tức bị block.

4. Tại sao web dùng Cloudflare/CDN bật Rate Limit lại bị block trắng toàn bộ người dùng?

Do Nginx lấy nhầm IP của Cloudflare làm IP người dùng, dẫn đến việc gộp chung hàng ngàn user vào một xô đếm và làm tràn xô ngay lập tức.

Cách fix: Khai báo dải IP của Cloudflare qua set_real_ip_from và dùng chỉ thị real_ip_header CF-Connecting-IP; để bóc tách đúng Real IP.

5. Có thể áp Rate Limit cho API nhưng chừa IP văn phòng công ty ra không?

Có. Dùng geo để gán cờ cho IP công ty, sau đó kết hợp map để trả về chuỗi rỗng (""). Nginx sẽ tự động bỏ qua, không đếm Rate Limit đối với các request có key rỗng.

6. Có nên dùng limit_conn để chặn DDoS (như HTTP Flood hay Slowloris) không?

Không hiệu quả. Nginx chỉ đếm connection vào limit_conn sau khi đã đọc xong toàn bộ HTTP header. Các đòn DDoS tinh vi như Slowloris cố tình ngâm header không bao giờ gửi xong, nên limit_conn hoàn toàn không nhận diện được. Khắc tinh thực sự để chặn DDoS ngâm kết nối là cấu hình client_header_timeoutclient_body_timeout thật ngắn.

7. Cấp 1MB RAM cho limit_req_zone thì theo dõi được bao nhiêu IP?

Khoảng 8.000 IP. Rất nhiều bài hướng dẫn trên mạng ghi là 16.000 IP, nhưng đó là thông số của máy chủ 32-bit cũ. Trên VPS 64-bit hiện đại, mỗi trạng thái tốn 128-byte bộ nhớ, nên 1MB chỉ lưu được khoảng 8.000 IP.

Kết luận

Kỹ thuật thiết lập Rate Limit trên Nginx tuy không thay thế được các hệ thống phòng chống DDoS Volumetric băng thông lớn, nhưng lại là tấm khiên vững chắc nhất cho VPS trước những đòn tấn công bào mòn (Low and Slow) và spam API Layer 7. Để máy chủ luôn ở trạng thái tốt nhất, hãy kết hợp giới hạn request với việc giám sát VPS bằng OpenTelemetry và Grafana để phát hiện sớm các bất thường.

Checklist vận hành hạ tầng không thể bỏ qua:

  1. ✅ Luôn tính toán bộ nhớ Zone theo kiến trúc 64-bit (128 byte/trạng thái đối với limit_req).
  2. ✅ Trị Slowloris bằng client_header_timeout thay vì trông chờ vào limit_conn.
  3. ✅ Bóc tách Real IP bằng X-Forwarded-For hoặc CF-Connecting-IP khi qua proxy.
  4. ✅ Luôn đi kèm tham số nodelay cùng với burst để tối ưu trải nghiệm người dùng thực.
  5. ✅ Chuẩn hóa phản hồi JSON 429 và sử dụng Fail2ban làm lớp phòng ngự bọc lót ở tầng mạng.

Tài liệu tham khảo