Nginx Limit Request: Tấm khiên chống DDoS ‘du kích’ và điều tiết Traffic hiệu quả

Security tutorial - IT technology blog
Security tutorial - IT technology blog

Trải nghiệm thực tế: Khi server bỗng dưng “ngạt thở”

Sau 6 tháng “thực chiến” quản lý hơn 10 cụm server production, mình nhận ra một lỗ hổng chí mạng: hầu hết website vừa và nhỏ đều bỏ trống hoàn toàn cửa ngõ HTTP. Nhiều anh em mải mê tối ưu code, nhưng lại quên rằng chỉ cần một script Python 5 dòng chạy vòng lặp cũng đủ khiến Nginx treo cứng.

Mình từng xử lý một ca “khó đẻ”: website bán hàng bị đối thủ spam liên tục 50-100 request/giây vào ô tìm kiếm. Traffic này không đủ làm sập băng thông (Volumetric DDoS), nhưng nó khiến MySQL nhảy vọt lên 100% CPU. Kết quả là khách hàng thật không thể thanh toán. Đây chính là lúc Limit Request Module phát huy vai trò cứu cánh.

So sánh các lớp phòng thủ phổ biến

Trước khi bắt tay vào cấu hình, hãy cùng điểm qua các tầng bảo vệ để bạn biết mình đang đứng ở đâu:

  • Cloudflare (WAF/CDN): Lớp giáp ngoài cùng cực mạnh, lọc traffic rác trước khi tới server. Tuy nhiên, nếu bạn để lộ IP gốc (Origin IP), lớp giáp này coi như vô dụng.
  • Firewall (iptables/nftables): Chặn ở tầng Network (Layer 3/4). Nó xử lý cực nhanh nhưng lại “mù tịt” về nội dung. Firewall không phân biệt được đâu là người dùng thật, đâu là bot đang cào dữ liệu.
  • Nginx Limit Request (Layer 7): Chốt chặn cuối cùng tại tầng ứng dụng. Nó hiểu rõ từng URL, IP và Session. Đây là công cụ hoàn hảo để xử lý các đợt tấn công nhỏ hoặc ngăn chặn tình trạng cào dữ liệu (web scraping) quá đà.

Tại sao thuật toán Leaky Bucket lại hiệu quả?

Nginx sử dụng thuật toán Leaky Bucket (Cái xô thủng) để điều tiết traffic. Hãy tưởng tượng request là nước đổ vào xô, và Nginx xử lý chúng thông qua một cái lỗ nhỏ dưới đáy với tốc độ cố định. Nếu nước đổ vào quá nhanh làm tràn xô, các request thừa sẽ bị từ chối ngay lập tức. Cơ chế này giúp server giữ nhịp độ ổn định, tránh tình trạng “sốc nhiệt” khi traffic tăng đột biến.

Hướng dẫn triển khai chi tiết

Mọi cấu hình sẽ diễn ra trong file nginx.conf hoặc các file cấu hình site trong sites-available/.

Bước 1: Thiết lập vùng nhớ dùng chung (Shared Memory Zone)

Nginx cần một nơi để ghi nhớ IP nào đã gửi bao nhiêu request. Hãy thêm dòng sau vào block http:

http {
    # 10MB có thể lưu trữ khoảng 160.000 trạng thái IP
    limit_req_zone $binary_remote_addr zone=mylimit:10m rate=10r/s;
}

Giải mã thông số:

  • $binary_remote_addr: Lưu IP dưới dạng nhị phân để tiết kiệm bộ nhớ (chỉ tốn 64 bytes cho mỗi trạng thái trên hệ thống 64-bit).
  • zone=mylimit:10m: Tạo vùng nhớ tên “mylimit” dung lượng 10MB.
  • rate=10r/s: Ngưỡng tối đa 10 request mỗi giây cho mỗi IP.

Bước 2: Áp dụng vào các khu vực nhạy cảm

Đừng áp dụng mù quáng cho toàn bộ site. Hãy tập trung vào những nơi tốn tài nguyên nhất như trang tìm kiếm hoặc login.

server {
    location /search/ {
        # Cho phép "vọt" lên thêm 20 request nhưng xử lý ngay lập tức
        limit_req zone=mylimit burst=20 nodelay;
        proxy_pass http://backend_app;
    }
}

Bước 3: Hiểu sâu về Burst và Nodelay

Đây là phần dễ gây nhầm lẫn nhất dẫn đến việc chặn nhầm người dùng tốt:

  • Burst=20: Giống như hàng chờ tại quầy vé. Nếu khách đến quá nhanh, họ có thể đứng đợi trong hàng chờ (tối đa 20 người) thay vì bị đuổi về ngay.
  • nodelay: Nếu không có từ khóa này, Nginx sẽ bắt khách hàng đợi đúng tốc độ 10r/s (rất chậm). Có nodelay, 20 request trong hàng chờ sẽ được xử lý ngay lập tức, nhưng các request thứ 21 trở đi sẽ bị báo lỗi ngay.

Tối ưu trải nghiệm với mã lỗi 429

Mặc định Nginx trả về lỗi 503 (Service Unavailable). Tuy nhiên, để thân thiện hơn với các bot “sạch” như Googlebot, bạn nên dùng mã 429 (Too Many Requests).

limit_req_status 429;
limit_req_log_level warn;

Việc ghi log ở mức warn giúp bạn dễ dàng theo dõi những IP nghi vấn qua file error.log mà không làm đầy ổ cứng quá nhanh.

Kiểm tra sức chịu tải

Đừng đợi đến khi bị tấn công mới biết mình cấu hình sai. Bạn có thể dùng tool ab (Apache Benchmark) để giả lập 100 request dồn dập:

ab -n 100 -c 10 http://yourdomain.com/search/

Sau đó kiểm tra log: tail -f /var/log/nginx/error.log. Nếu thấy thông báo “limiting requests…” xuất hiện, hệ thống của bạn đã được bảo vệ.

Kinh nghiệm “xương máu” từ thực tế

  1. Tránh siết quá chặt: Một trang web hiện đại có thể tải 30-50 file static (CSS, JS, ảnh) cùng lúc. Nếu bạn đặt rate quá thấp (ví dụ 2r/s) mà không có burst, layout website sẽ bị vỡ nát.
  2. White-list cho nội bộ: Luôn dùng module geo để loại trừ IP văn phòng hoặc IP của các dịch vụ giám sát (UptimeRobot, Checkly).
  3. Combo Nginx + Fail2Ban: Nginx chỉ chặn request tại thời điểm đó. Để cấm cửa vĩnh viễn các IP cứng đầu, hãy dùng Fail2Ban để quét log Nginx và đẩy IP đó vào Firewall của hệ điều hành.

Chống DDoS là một cuộc đua vũ trang không hồi kết. Tuy nhiên, làm chủ Nginx Limit Request sẽ giúp server của bạn trụ vững trước 90% các loại bot phá hoại phổ thông hiện nay.

Share: