Khi một VPS có IP public và mở cổng SSH mặc định ra Internet, dịch vụ này thường sớm nhận các kết nối thăm dò từ những đợt quét tự động. Các script trên mạng liên tục thử mật khẩu với những tài khoản quen thuộc như root, admin hay ubuntu. Dù bạn đã chuyển sang xác thực bằng SSH key và tắt đăng nhập bằng mật khẩu (như khuyến nghị trong bài viết về bảo mật SSH key truy cập VPS), việc máy chủ liên tục tiếp nhận bắt tay TCP và ghi log thất bại vẫn gây phân tán tài nguyên theo dõi.
Để lọc bớt những kết nối này ở tầng mạng, Fail2ban vẫn là giải pháp gọn gàng và dễ triển khai trên các máy chủ đơn lẻ. Về cơ bản, phần mềm theo dõi log của dịch vụ, đếm số lần xác thực sai từ một địa chỉ IP trong khoảng thời gian nhất định, rồi gọi tường lửa để tạm thời chặn IP đó nếu vượt ngưỡng cho phép.
Dù nguyên lý hoạt động không đổi, cách cấu hình Fail2ban trên các bản phân phối Linux hiện nay (như Ubuntu 22.04, Ubuntu 24.04 hay Debian 12) cần được cập nhật so với các tài liệu cũ:
- Tường lửa mặc định đã chuyển sang
nftables: Thay vì chèn từng dòng quy tắc riêng lẻ vào iptables khiến máy chủ phải duyệt lần lượt từng IP bị cấm, Fail2ban có thể đẩy IP vào một tập hợp băm (nft set) trong kernel. Dù danh sách cấm lên tới hàng nghìn IP, hệ thống chỉ cần kiểm tra đúng một lần là biết kết quả, không làm trễ gói tin mạng. - Log hệ thống quản lý qua
systemd-journald: Nhiều bản cài đặt máy chủ tối giản không còn cài sẵnrsyslog, dẫn đến việc file/var/log/auth.logkhông tồn tại hoặc không ghi dữ liệu mới. Fail2ban cần đọc trực tiếp từ journald để không bỏ sót sự kiện. - Cơ chế phạt tăng dần (Bantime Increment): Thay vì áp đặt một mức thời gian cấm cố định, hệ thống có thể tự động nâng thời gian chặn theo cấp số nhân nếu cùng một IP tiếp tục quay lại dò quét sau khi hết hạn.
Luồng xử lý từ systemd journal qua bộ lọc Fail2ban đến tập hợp nftables trong Linux kernel.
Cài đặt các gói phụ thuộc cần thiết
Trên Debian 12 và Ubuntu 22.04/24.04, ngoài gói fail2ban, bạn cần cài đặt thêm gói hỗ trợ đọc log trực tiếp từ systemd cho Python. Nếu thiếu gói này, Fail2ban sẽ không thể kích hoạt backend systemd:
sudo apt update
sudo apt install fail2ban python3-systemd nftables -ySau khi cài đặt xong, kiểm tra trạng thái dịch vụ bằng lệnh:
sudo systemctl status fail2banDịch vụ mặc định sẽ tự khởi chạy, nhưng chúng ta cần điều chỉnh cấu hình trước khi để nó hoạt động chính thức trên môi trường production.
Chuẩn hóa file cấu hình: Không sửa jail.conf
Thư mục /etc/fail2ban/ chứa file cấu hình mặc định là jail.conf. File này do nhà phát triển và ban quản lý gói của hệ điều hành bảo trì.
Nguyên tắc bất di bất dịch khi làm việc với Fail2ban là không bao giờ sửa trực tiếp vào jail.conf. Khi bạn chạy lệnh nâng cấp hệ thống (apt upgrade), nếu gói Fail2ban có bản cập nhật mới, tiến trình cài đặt sẽ hỏi bạn có muốn ghi đè file jail.conf hay không. Nếu người vận hành chọn ghi đè, toàn bộ các quy tắc tùy chỉnh của bạn sẽ biến mất hoàn toàn.
Fail2ban nạp cấu hình theo thứ tự ưu tiên phân tầng:
- Đọc file gốc:
/etc/fail2ban/jail.conf - Đọc file ghi đè toàn cục:
/etc/fail2ban/jail.local - Đọc các file cấu hình thành phần:
/etc/fail2ban/jail.d/*.confvà/etc/fail2ban/jail.d/*.local
Mọi thông số được định nghĩa trong các file .local sẽ tự động ghi đè giá trị tương ứng trong file .conf. Do đó, chúng ta sẽ tạo một file cấu hình riêng tại /etc/fail2ban/jail.local.
Thiết lập khối DEFAULT: nftables, systemd và chống tự khóa IP
Mở file /etc/fail2ban/jail.local bằng trình soạn thảo:
sudo nano /etc/fail2ban/jail.localDán vào nội dung chuẩn hóa sau:
[DEFAULT]
# 1. Danh sách IP loại trừ: Không bao giờ cấm IP quản trị và dải loopback
ignoreip = 127.0.0.1/8 ::1 14.241.120.45
# 2. Sử dụng nftables với cấu trúc bảng băm (Set) thay cho iptables tuyến tính
banaction = nftables-multiport
banaction_allports = nftables-allports
# 3. Đọc log trực tiếp từ socket systemd-journald
backend = systemd
# 4. Kích hoạt cơ chế phạt lũy tiến theo cấp số nhân
bantime.increment = true
bantime = 10m
bantime.rndtime = 5m
bantime.maxtime = 5w
bantime.factor = 2
# Ngưỡng phát hiện mặc định
findtime = 10m
maxretry = 5Hãy xem xét kỹ từng tham số cốt lõi trong khối này:
Bảo vệ IP quản trị với ignoreip
ignoreip là danh sách các địa chỉ IP, dải CIDR hoặc tên miền mà Fail2ban cam kết bỏ qua, không bao giờ chặn dù có ghi nhận bao nhiêu lượt đăng nhập lỗi đi chăng nữa. Luôn giữ dải 127.0.0.1/8 và ::1 cho các kết nối nội bộ. Thêm IP tĩnh của văn phòng hoặc máy tính làm việc của bạn vào danh sách, ngăn cách bằng dấu cách.
Nếu bạn dùng kết nối mạng gia đình với IP động thay đổi liên tục, việc thêm IP tĩnh sẽ không khả thi. Trong trường hợp này:
- Giữ
maxretryở mức từ 5 đến 6 lần để không bị khóa chỉ vì gõ nhầm một hoặc hai lần. - Đặt thời gian cấm ban đầu vừa phải (
bantime = 10m) thay vì cấm vĩnh viễn. Nếu chẳng may bị khóa, bạn chỉ cần đợi 10 phút hoặc đổi sang kết nối 4G trên điện thoại để truy cập lại máy chủ. - Luôn giữ một cửa sổ SSH đang kết nối sẵn khi chỉnh sửa cấu hình tường lửa, đồng thời mở một tab terminal thứ hai để thử nghiệm kết nối mới. Nếu tab mới bị lỗi, tab cũ vẫn còn nguyên để bạn sửa sai.
Cơ chế tập hợp băm (Set) của nftables
Khi bạn chỉ định banaction = nftables-multiport, Fail2ban sẽ tạo một bảng riêng trong kernel Linux có tên là inet f2b-table. Bên trong bảng này, một tập hợp băm (set) được khởi tạo.
Khi một IP bị cấm, Fail2ban chỉ thêm địa chỉ đó vào tập hợp có sẵn. Mỗi khi có gói tin mạng đi vào VPS, kernel Linux đối chiếu IP nguồn với tập hợp này trong đúng một bước xử lý duy nhất. Cách làm này khác hoàn toàn với iptables truyền thống — nơi mỗi IP bị cấm là một dòng quy tắc độc lập, buộc máy chủ phải duyệt tuần tự từ đầu đến cuối danh sách. Khi danh sách cấm phình to đến vài nghìn địa chỉ, cơ chế tập hợp của nftables giúp việc lọc gói tin diễn ra tức thì, không làm tăng độ trễ mạng và không tiêu tốn chu kỳ xử lý của CPU.
Đọc log từ systemd journal
Khai báo backend = systemd yêu cầu Fail2ban giao tiếp trực tiếp với dịch vụ systemd-journald. Fail2ban sẽ tự động trích xuất các dòng log liên quan đến service cần bảo vệ thông qua bộ lọc đơn vị (unit filter), ví dụ _SYSTEMD_UNIT=ssh.service. Bạn không cần bận tâm máy chủ có đang ghi log ra file text thô hay không, và không gặp tình trạng dịch vụ báo lỗi khi file log bị xoay vòng (logrotate).
Cơ chế phạt tăng dần (Exponential Backoff)
Với bantime.increment = true:
- Lần đầu vi phạm: IP bị cấm trong 10 phút (
bantime = 10m). - Tái phạm lần thứ hai: Thời gian cấm tự động nhân đôi thành 20 phút (
bantime.factor = 2). - Tái phạm nhiều lần: Thời gian phạt tăng dần lên vài giờ, vài ngày và tối đa là 5 tuần (
bantime.maxtime = 5w). bantime.rndtime = 5msẽ thêm một độ lệch ngẫu nhiên vào thời gian cấm, khiến các botnet được lập trình canh đúng giây hết hạn để quét lại cũng không thể dự đoán chính xác thời điểm được mở khóa.
Cấu hình Jail bảo vệ SSH
Sau khối [DEFAULT], thêm khối định nghĩa cho dịch vụ SSH ngay trong file /etc/fail2ban/jail.local:
[sshd]
enabled = true
port = ssh
filter = sshd
maxretry = 3
findtime = 15m
bantime = 1hNếu bạn đã đổi cổng SSH của máy chủ sang một cổng khác (ví dụ cổng 2222 như hướng dẫn trong bài viết về các cổng tường lửa cần mở trên VPS), hãy sửa dòng port:
port = 2222Lưu file cấu hình lại. Kiểm tra tính hợp lệ của cấu hình trước khi nạp lại dịch vụ:
sudo fail2ban-client -dLệnh này sẽ in ra toàn bộ cấu hình đã được phân giải mà không làm thay đổi trạng thái đang chạy của dịch vụ. Nếu không có thông báo lỗi cú pháp, tiến hành khởi động lại Fail2ban:
sudo systemctl restart fail2banKiểm tra xem jail sshd đã hoạt động hay chưa:
sudo fail2ban-client status sshdĐầu ra mẫu cho thấy jail đã hoạt động bình thường:
Status for the jail: sshd
|- Filter
| |- Currently failed: 0
| |- Total failed: 12
| `- File list:
`- Actions
|- Currently banned: 2
|- Total banned: 5
`- Banned IP list: 198.51.100.23 203.0.113.88Để xác nhận xem các IP này đã thực sự được nạp vào bảng băm của nftables ở tầng kernel hay chưa, bạn chạy lệnh kiểm tra set của nftables:
sudo nft list set inet f2b-table addr-set-sshdKết quả sẽ liệt kê danh sách các IP đang bị khóa trực tiếp trong kernel:
table inet f2b-table {
set addr-set-sshd {
type ipv4_addr
elements = { 198.51.100.23, 203.0.113.88 }
}
}Cấu hình Jail bảo vệ Nginx và cái bẫy đứng sau Cloudflare Proxy
Bảo vệ Nginx bằng Fail2ban cần lưu ý thêm một bước quan trọng nếu website đang chạy qua dịch vụ Cloudflare Proxy.
Rủi ro sập toàn bộ website nếu cấu hình sai
Khi bật Cloudflare Proxy, mọi kết nối từ trình duyệt người dùng đều đi qua máy chủ biên (Edge Server) của Cloudflare trước khi được chuyển tiếp về VPS của bạn. Địa chỉ IP nguồn mà cổng mạng của VPS tiếp nhận là IP của Cloudflare (thuộc các dải như 173.245.48.0/20, 103.21.244.0/22).
Nếu bạn bật jail Nginx và sử dụng banaction = nftables-multiport:
- Một kẻ tấn công thực hiện cào quét hoặc dò tìm trang quản trị
wp-login.php. - Nginx ghi nhận log truy vấn. Nếu Nginx chưa được cấu hình khôi phục IP thật, IP xuất hiện trong log là IP của Cloudflare.
- Fail2ban đọc log, thấy IP Cloudflare vượt ngưỡng vi phạm và gọi
nftableschặn IP đó ở cổng 443 của VPS. - Hàng nghìn người dùng bình thường khác đang được Cloudflare định tuyến qua cùng máy chủ biên đó sẽ lập tức bị đứt kết nối, nhận lỗi 502 Bad Gateway hoặc 504 Gateway Timeout. Nếu Fail2ban chặn tiếp vài IP biên nữa của Cloudflare, toàn bộ website sẽ biến mất hoàn toàn đối với mọi người dùng trên Internet.
Bên cạnh nguy cơ chặn nhầm IP biên, việc đặt VPS sau CDN Proxy còn ảnh hưởng đến nhiều tác vụ hạ tầng khác như lỗi Certbot gia hạn SSL thất bại khi bật Cloudflare Proxy do cơ chế HTTP-01 challenge bị WAF can thiệp. Vì vậy, cấu hình phân biệt IP biên và IP client thực tế là bước bắt buộc.
Bước 1: Khôi phục IP thật trong Nginx (Real IP Module)
Trước khi để Fail2ban đọc bất kỳ file log nào của Nginx, bạn bắt buộc phải cấu hình Nginx tin cậy các dải IP của Cloudflare và lấy IP người dùng từ header CF-Connecting-IP.
Mở file cấu hình Nginx (thường tại /etc/nginx/conf.d/cloudflare-real-ip.conf):
# Danh sách các dải IP chính thức của Cloudflare (IPv4)
set_real_ip_from 173.245.48.0/20;
set_real_ip_from 103.21.244.0/22;
set_real_ip_from 103.22.200.0/22;
set_real_ip_from 103.31.4.0/22;
set_real_ip_from 141.101.64.0/18;
set_real_ip_from 108.162.192.0/18;
set_real_ip_from 190.93.240.0/20;
set_real_ip_from 188.114.96.0/20;
set_real_ip_from 197.234.240.0/22;
set_real_ip_from 198.41.128.0/17;
set_real_ip_from 162.158.0.0/15;
set_real_ip_from 104.16.0.0/13;
set_real_ip_from 104.24.0.0/14;
set_real_ip_from 172.64.0.0/13;
set_real_ip_from 131.0.72.0/22;
# Lấy IP thực của client từ header do Cloudflare gửi kèm
real_ip_header CF-Connecting-IP;Kiểm tra cú pháp và nạp lại Nginx:
sudo nginx -t
sudo systemctl reload nginxKiểm tra lại file /var/log/nginx/access.log. Khi có truy cập từ bên ngoài, cột IP đầu tiên trong log phải là IP thật của thiết bị người dùng, không phải IP bắt đầu bằng 172.64.x.x hay 162.158.x.x.
Bước 2: Lựa chọn cơ chế chặn phù hợp
Khi Nginx đã ghi nhận đúng IP thật:
- Nếu traffic đi qua Cloudflare: Lệnh cấm ở tường lửa
nftablescục bộ trên VPS sẽ không chặn được kẻ tấn công (bởi vì gói tin đi tới cổng mạng VPS mang IP nguồn là Cloudflare, trong khi nftables lại đang chặn IP thật của kẻ đó). Với dạng traffic này, giải pháp an toàn và hiệu quả nhất là bật WAF Rate Limiting trực tiếp trên trang quản trị Cloudflare, hoặc cấu hình Fail2ban sử dụngaction = cloudflaređể gọi API Cloudflare tạo Firewall Rule chặn ở rìa mạng CDN. - Nếu kẻ tấn công quét trực tiếp vào IP gốc của VPS (Bypass Cloudflare): Đây là lúc Fail2ban kết hợp
nftablesphát huy tối đa tác dụng. Mọi request gửi thẳng vào IP máy chủ mà không đi qua tên miền sẽ bị Nginx từ chối hoặc trả về mã 444/403, và Fail2ban sẽ khóa vĩnh viễn IP đó ở cổng mạng.
Tạo cấu hình bảo vệ Nginx chống quét mã độc tại /etc/fail2ban/jail.d/nginx-botsearch.local:
[nginx-botsearch]
enabled = true
port = http,https
logpath = /var/log/nginx/access.log
filter = nginx-botsearch
maxretry = 2
findtime = 10m
bantime = 24hBộ lọc nginx-botsearch có sẵn trong /etc/fail2ban/filter.d/nginx-botsearch.conf, chuyên bắt các truy vấn tìm kiếm lỗ hổng phổ biến như phpmyadmin, setup.php, .env, wp-login hay các file cấu hình nhạy cảm.
Thử nghiệm bộ lọc Regex với fail2ban-regex
Trước khi bật bất kỳ jail mới nào, bạn luôn nên kiểm tra xem bộ lọc regex có hoạt động chính xác với file log thực tế của hệ thống hay không. Việc này giúp bạn tránh tình trạng viết sai cú pháp regex hoặc cấu trúc log không khớp, dẫn đến việc Fail2ban quét hàng triệu dòng log mà không phát hiện được gì hoặc bắt nhầm người dùng hợp lệ.
Fail2ban cung cấp công cụ kiểm tra cú pháp dòng lệnh fail2ban-regex:
sudo fail2ban-regex /var/log/nginx/access.log /etc/fail2ban/filter.d/nginx-botsearch.confCông cụ sẽ đọc file log từ đầu đến cuối và in ra bảng tổng kết:
Running tests
=============
Use failregex filter file : nginx-botsearch, basedir: /etc/fail2ban
Use log file : /var/log/nginx/access.log
Use encoding : UTF-8
Results
=======
Failregex: 47 total
|- [#] Hit count
| [1] 32 hit(s): /{0,2}(?:(?:phpMyAdmin|phpmyadmin).*)$
| [2] 15 hit(s): \.(?:env|git|aws|ssh)
`-
Ignoreregex: 0 total
Date template hits:
|- [#] (hit) Profile
| [0] (1482) DatePattern[Day/Month/Year:Hour:Minute:Second Zone]
`-
Lines: 1482 lines, 0 ignored, 47 matched, 1435 missedCác thông số cần chú ý:
matched: Số lượng dòng log khớp với mẫu vi phạm và sẽ bị tính điểm phạt.missed: Số dòng log bình thường được bỏ qua.- Nếu
matched = 0dù bạn biết chắc file log đang chứa các dòng quét độc hại, hãy kiểm tra lại định dạng ngày tháng (DatePattern) hoặc cấu trúc log format của Nginx xem có khác biệt so với mặc định không.
Các lệnh vận hành và xử lý sự cố thường dùng
Trong quá trình vận hành, người quản trị cần nắm rõ các câu lệnh điều khiển trực tiếp qua fail2ban-client.
Xem trạng thái tổng thể và danh sách jail đang chạy
sudo fail2ban-client statusĐầu ra sẽ cho biết số lượng jail đang hoạt động:
Status
|- Number of jail: 2
`- Jail list: nginx-botsearch, sshdKiểm tra chi tiết một jail cụ thể
sudo fail2ban-client status sshdLệnh này cung cấp danh sách đầy đủ các IP đang bị cấm (Banned IP list) và tổng số lần thất bại đã ghi nhận.
Gỡ ban một IP khẩn cấp (Unban)
Nếu một thành viên trong nhóm kỹ thuật hoặc chính bạn bị khóa nhầm do đổi mạng, dùng lệnh sau để mở khóa lập tức cho IP đó:
# Cú pháp: sudo fail2ban-client set <tên-jail> unbanip <địa-chỉ-IP>
sudo fail2ban-client set sshd unbanip 14.241.120.45Nếu IP đó bị cấm trên nhiều jail khác nhau, bạn có thể chạy lệnh unban cho từng jail hoặc dùng cờ unban toàn bộ:
sudo fail2ban-client unban 14.241.120.45Sau khi chạy lệnh unban, kiểm tra lại danh sách set của nftables để chắc chắn IP đã được giải phóng khỏi kernel:
sudo nft list set inet f2b-table addr-set-sshdXem log hoạt động nội bộ của Fail2ban
Khi một jail không hoạt động như mong đợi hoặc không rõ vì sao một IP bị cấm, hãy xem log ghi nhận của Fail2ban:
sudo journalctl -u fail2ban -n 50 --no-pagerHoặc theo dõi trực tiếp file log nội bộ nếu được bật:
sudo tail -f /var/log/fail2ban.logTrong log, các sự kiện quan trọng cần chú ý:
NOTICE [sshd] Ban 198.51.100.23: IP bị cấm thành công.NOTICE [sshd] Unban 198.51.100.23: IP đã hết hạn cấm và được gỡ bỏ khỏi firewall.ERROR: Lỗi liên quan đến regex, quyền đọc file log hoặc không tìm thấy socket backend.
Checklist kiểm tra an toàn trước khi rời máy chủ
Sau khi hoàn tất cấu hình, người quản trị nên rà soát lại các mục sau để đảm bảo hệ thống vừa được bảo vệ vừa không gây gián đoạn công việc:
- Đã khai báo đầy đủ IP tĩnh và mạng nội bộ trong tham số
ignoreiptại file/etc/fail2ban/jail.local. - Đã chuyển
banactionsangnftables-multiportvà kiểm tra lệnhnft list tablescó hiển thịinet f2b-table. - Đã cấu hình
backend = systemdtrên các máy chủ không chạyrsyslog. - Không chặn trực tiếp các dải IP của Cloudflare ở tầng tường lửa cục bộ nếu website sử dụng dịch vụ CDN Proxy.
- Đã thử nghiệm mở một cửa sổ SSH mới từ một máy tính khác để xác nhận cổng quản trị vẫn kết nối thông suốt trước khi đóng phiên làm việc hiện tại.



