Nhật ký chống DDoS trên VPS: Từ sự cố 502 đến kiến trúc phòng thủ 5 lớp & báo cáo thực nghiệm Locust
Tấn công từ chối dịch vụ (DoS/DDoS) luôn là nỗi ác mộng đối với các quản trị viên hệ thống VPS vừa và nhỏ. Khi bị vắt cạn tài nguyên, web server thường trả về lỗi 502 Bad Gateway hoặc hoàn toàn đơ cứng.
Trong bài viết này, tôi chia sẻ chi tiết trải nghiệm thực tế từ lúc hệ thống keoquy.com bị sập do quá tải Event Loop của Next.js, cho đến quá trình xây dựng hệ thống phòng thủ 5 lớp 100% Self-Hosted (không phụ thuộc Cloudflare) và phân tích báo cáo kiểm thử chịu tải thực tế với 2,000 user giả lập (Locust).

1. Nguyên nhân gốc rễ: Tại sao Web Server dính lỗi 502 Bad Gateway?
Trong đợt thử nghiệm đầu tiên bằng script Python đơn giản (python-requests), lượng kết nối TCP tạo ra đồng thời vượt quá 5,000 kết nối.
Sơ đồ luồng gây nghẽn:
- Event Loop Single-Thread: Next.js mặc định chạy trên 1 tiến trình Node.js (1 CPU core). Với lượng request dồn dập, Event Loop bị nghẽn (Block Event Loop).
- Backlog Queue tràn ngập: Hàng đợi kết nối TCP socket bị đầy. Caddy Web Server (Reverse Proxy) gửi request tới port 3000 nhưng Node.js không phản hồi kịp.
- Caddy 502 Bad Gateway: Caddy nhận phản hồi
connection reset by peerhoặcconnection refusedtừ upstream 127.0.0.1:3000, lập tức trả lỗi 502 về cho người dùng.
2. Chi tiết 5 lớp phòng thủ Self-Hosted tối ưu
Để bảo vệ VPS chống lại các đợt DoS/DDoS lớp 4 (L4) và lớp 7 (L7) mà không cần đến proxy trung gian, chúng tôi thiết lập kiến trúc 5 tầng chuyên sâu:

Lớp 1: Linux Kernel sysctl (Tối ưu Socket & Anti-SYN Flood)
Cấu hình tại /etc/sysctl.d/99-anti-ddos.conf:
net.ipv4.tcp_syncookies = 1: Kích hoạt SYN Cookies bảo vệ khỏi SYN flood.net.core.somaxconn = 65535: Mở rộng hàng đợi kết nối TCP.net.ipv4.tcp_max_syn_backlog = 65535: Tăng dung lượng hàng đợi SYN.net.ipv4.tcp_fin_timeout = 15: Rút ngắn thời gian thu hồi socket FIN-WAIT-2.
Lớp 2: iptables Connlimit Firewall (Chặn đứng tại Tầng Kernel)
Giới hạn mỗi IP chỉ được phép mở tối đa 40 kết nối TCP đồng thời tới port 80/443:
sudo iptables -A INPUT -p tcp --syn --dport 443 -m connlimit --connlimit-above 40 -j DROP
sudo iptables -A INPUT -p tcp --syn --dport 80 -m connlimit --connlimit-above 40 -j DROP
Kết quả: Khi botnet tạo quá 40 kết nối, Kernel lập tức DROP gói tin SYN mà không tốn tài nguyên xử lý ở các tầng trên.
Lớp 3: Caddy Web Server (Bot Filtering & JSON Access Logs)
Chặn các User-Agent công cụ cào dữ liệu / script bot phổ biến và ghi nhận log JSON:
@blocked_bots {
header_regexp User-Agent "(?i)(python-requests|httpx|Go-http-client|Scrapy|libwww-perl)"
}
respond @blocked_bots 403

Lớp 4: Fail2ban (Tự động khóa IP vi phạm)
Cấu hình Jail caddy-ddos tự động chặn IP phát sinh quá 15 lỗi (403, 429, 502) trong 60 giây. IP vi phạm bị DROP ở firewall trong 3,600 giây (1 giờ).
Lớp 5: Application Scaling với PM2 Cluster Mode (100% CPU Cores)
Chuyển Next.js từ đơn luồng sang chạy Cluster trên toàn bộ 6 nhân CPU (ecosystem.config.js):
module.exports = {
apps: [{
name: 'keoquy-frontend',
script: 'node_modules/next/dist/bin/next',
args: 'start -p 3000',
instances: 'max', // Tận dụng 6/6 CPU Cores
exec_mode: 'cluster'
}]
};
3. Báo cáo thực nghiệm chịu tải với Locust (2,000 Users)
Sau khi hoàn tất cấu hình 5 lớp, chúng tôi tiến hành đợt Load Test thực tế lớn nhất sử dụng framework Locust với 2,000 user giả lập truy cập liên tục.

Thông số kiểm thử & Kết quả chi tiết từ Báo cáo Locust:
| Thông số / Chỉ số | Giá trị thực tế ghi nhận | Phân tích kỹ thuật |
|---|---|---|
| Target Host | https://keoquy.com |
Server VPS 6 Cores, RAM 16GB |
| Simulated Users | 2,000 Users | Spawn rate 50 user/s |
| Tổng số Requests | 84,809 Request | Diễn ra trong 4 phút |
| Lưu lượng trung bình (RPS) | 353.0 Req/s | Tải xử lý trực tiếp |
| Số Request bị chặn (Dropped) | 58,542 (69.0%) | Chặn bởi iptables connlimit |
| Số Request thành công (200 OK) | 26,267 (31.0%) | Phản hồi mượt mà 5-7ms |
| Lỗi 502 Bad Gateway | 0 lỗi (0%) | Node.js Cluster tải mượt |
Phân tích lỗi ConnectTimeoutError trong Locust Report:
Trong báo cáo Locust, 58,542 failures ghi nhận dưới dạng ConnectTimeoutError (Connection to keoquy.com timed out).
Đây không phải lỗi sập server, mà chính là bằng chứng cho thấy Lớp 2 (iptables connlimit) hoạt động hoàn hảo: Khi IP test vượt quá 40 kết nối TCP, Kernel thực hiện DROP gói tin SYN. Phía client test không nhận được ACK nên bị timeout, giúp CPU và RAM của VPS hoàn toàn bình tĩnh ở mức < 20%!
4. Tổng kết & Bài học kinh nghiệm
- Self-Hosted Anti-DDoS hoàn toàn khả thi đối với các đợt tấn công từ số lượng IP hạn chế hoặc script bot tự động.
- Quy tắc vàng: Giữ kết nối tấn công càng xa ứng dụng (Application Layer) càng tốt. Chặn ở
iptables DROPtiêu tốn gần như 0% CPU so với việc để request đi vào Node.js / Python / Go. - PM2 Cluster Mode là bắt buộc đối với Next.js khi triển khai trên VPS đa nhân để tránh nghẽn Event Loop.
Hy vọng bài chia sẻ cùng báo cáo thực nghiệm này mang lại nhiều giá trị hữu ích cho các bạn đang quản trị VPS!


