❤️ AZDIGI chính thức cập nhật hệ thống blog mới hoàn chỉnh. Tuy nhiên có thể một số bài viết bị sai lệch hình ảnh, hãy ấn nút Báo cáo bài viết ở cuối bài để AZDIGI cập nhật trong thời gian nhanh nhất. Chân thành cám ơn.

Nếu bạn đã theo serie Linux từ đầu, chắc hẳn đã quen với việc ufw allow hay firewall-cmd --add-service để mở port. Nhưng trên production server, chỉ allow/deny thôi chưa đủ. Bạn cần giới hạn tần suất để chặn dò mật khẩu, custom rules để chống DDoS, xử lý Docker bypass firewall, và hàng loạt kỹ thuật khác.

Firewall nâng cao VPS

Bài này mình sẽ đi sâu vào những pattern firewall thực tế mà bạn sẽ cần khi vận hành server production. Vẫn là UFW (Ubuntu/Debian) và firewalld (AlmaLinux/Rocky), nhưng ở một level khác.

Giới hạn tần suất với UFW

Giới hạn tần suất là cách đơn giản nhất để hạn chế dò mật khẩu. Thay vì chỉ ufw allow ssh, bạn dùng limit:

sudo ufw limit ssh/tcp

Lệnh này cho phép kết nối SSH nhưng sẽ từ chối nếu một IP tạo quá 6 connection trong 30 giây. Tức là nếu ai đó cố login liên tục (kiểu dò mật khẩu), họ sẽ bị block tạm thời.

Kiểm tra lại rule đã set:

sudo ufw status verbose

Bạn sẽ thấy dòng tương tự:

22/tcp                     LIMIT IN    Anywhere

Rate limit mặc định của UFW (6 connections / 30 giây) là cố định, không tùy chỉnh được qua command line. Nếu cần control chính xác hơn, bạn phải viết custom rules trong /etc/ufw/before.rules hoặc dùng fail2ban kết hợp.

Custom rules trong UFW, chống SYN flood và DDoS

UFW thực chất là frontend của iptables. Khi cần rules phức tạp hơn những gì command line cho phép, bạn chỉnh trực tiếp file /etc/ufw/before.rules. Đây là file chứa các iptables rules được load trước khi UFW rules chạy.

Mở file ra:

sudo nano /etc/ufw/before.rules

Thêm các rules sau vào trước dòng COMMIT cuối cùng (trong phần *filter):

# Chống SYN flood
-A ufw-before-input -p tcp --syn -m limit --limit 12/s --limit-burst 24 -j ACCEPT
-A ufw-before-input -p tcp --syn -j DROP

# Giới hạn ICMP (chống ping flood) -A ufw-before-input -p icmp --icmp-type echo-request -m limit --limit 1/s --limit-burst 4 -j ACCEPT -A ufw-before-input -p icmp --icmp-type echo-request -j DROP

# Drop invalid packets -A ufw-before-input -m conntrack --ctstate INVALID -j DROP

# Drop fragmented packets -A ufw-before-input -f -j DROP

Giải thích nhanh từng nhóm:

  • SYN flood protection: cho phép tối đa 12 SYN packets/giây, burst 24. Vượt quá thì drop. SYN flood là kiểu tấn công gửi hàng loạt SYN request mà không bao giờ hoàn tất handshake, làm server hết socket.
  • ICMP limit: chỉ cho 1 ping/giây. Đủ để check server sống hay chết, nhưng chặn được ping flood.
  • Invalid packets: những packet không thuộc connection nào đang mở, thường là scan hoặc tấn công.
  • Fragmented packets: packet bị chia nhỏ bất thường, thường dùng để bypass firewall rules.

Sau khi chỉnh xong, reload UFW:

sudo ufw reload

Cẩn thận khi chỉnh before.rules. Nếu syntax sai, UFW sẽ không load được và firewall có thể bị disable. Luôn test trên staging trước, hoặc ít nhất có console access (VNC/IPMI) phòng trường hợp bị lock out.

Docker bypass UFW, vấn đề nghiêm trọng mà ít người biết

Đây là một trong những “gotcha” kinh điển. Bạn cài Docker, tạo container expose port 3306 (MySQL), rồi yên tâm vì UFW đã chặn 3306 từ bên ngoài. Nhưng thực tế? MySQL vẫn accessible từ internet.

Lý do: Docker tự thêm iptables rules riêng, và rules này được insert trước UFW rules. Docker tạo chain riêng tên DOCKER trong iptables, hoàn toàn bỏ qua UFW.

Kiểm tra nhanh:

# Xem Docker đã thêm rules gì
sudo iptables -L DOCKER -n --line-numbers

Bạn sẽ thấy các ACCEPT rules cho port mà container expose, không qua UFW filter nào cả.

Cách fix 1: Dùng DOCKER-USER chain

Docker cung cấp chain DOCKER-USER để bạn thêm rules custom. Chain này được xử lý trước chain DOCKER, nên bạn có thể chặn traffic ở đây.

Thêm vào /etc/ufw/after.rules (cuối file, sau phần COMMIT hiện có):

*filter
:DOCKER-USER - [0:0]

# Cho phép traffic từ internal network -A DOCKER-USER -s 10.0.0.0/8 -j ACCEPT -A DOCKER-USER -s 172.16.0.0/12 -j ACCEPT -A DOCKER-USER -s 192.168.0.0/16 -j ACCEPT

# Cho phép established connections -A DOCKER-USER -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

# Cho phép HTTP/HTTPS từ bên ngoài (cho web container) -A DOCKER-USER -p tcp --dport 80 -j ACCEPT -A DOCKER-USER -p tcp --dport 443 -j ACCEPT

# Drop mọi thứ khác đến Docker containers -A DOCKER-USER -j DROP

COMMIT

Reload UFW:

sudo ufw reload

Giờ chỉ port 80 và 443 của Docker containers mới accessible từ bên ngoài. MySQL, Redis hay bất kỳ service nào khác chạy trong Docker đều bị chặn.

Cách fix 2: Tắt iptables của Docker

Nếu muốn kiểm soát hoàn toàn, bạn có thể bảo Docker không đụng vào iptables. Tạo hoặc chỉnh file /etc/docker/daemon.json:

{
  "iptables": false
}

Restart Docker:

sudo systemctl restart docker

Khi tắt iptables của Docker, container-to-container networking và port publishing sẽ không tự hoạt động. Bạn phải tự tạo iptables/UFW rules cho mọi port cần expose. Cách này cho control tuyệt đối nhưng cần nhiều công setup hơn. Mình khuyên dùng DOCKER-USER chain (cách 1) cho đa số trường hợp.

firewalld zones nâng cao và rich rules

Trên AlmaLinux/Rocky, firewalld có hệ thống zones mạnh hơn nhiều so với UFW. Ở bài trước bạn đã dùng zone public mặc định. Giờ mình sẽ tận dụng zones để phân tầng access.

Tạo custom zone

Ví dụ tạo zone riêng cho internal network:

# Tạo zone mới
sudo firewall-cmd --permanent --new-zone=internal-apps

# Thêm source IP range cho zone sudo firewall-cmd --permanent --zone=internal-apps --add-source=10.0.0.0/8

# Cho phép các service nội bộ trong zone này sudo firewall-cmd --permanent --zone=internal-apps --add-service=mysql sudo firewall-cmd --permanent --zone=internal-apps --add-service=redis

# Reload để áp dụng sudo firewall-cmd --reload

Với setup này, MySQL và Redis chỉ accessible từ dải 10.0.0.0/8 (internal network). Mọi IP khác kết nối đến sẽ bị zone public xử lý (mà public thì không mở MySQL/Redis).

Rich rules, kiểm soát chi tiết

Rich rules cho phép bạn viết rules phức tạp hơn, kết hợp source IP, port, protocol, và action trong một rule duy nhất:

# Cho phép MySQL chỉ từ dải IP nội bộ
sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="10.0.0.0/8" service name="mysql" accept'

# Cho phép IP cụ thể SSH (ví dụ: office IP) sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="203.0.113.50" service name="ssh" accept'

# Chặn một IP cụ thể hoàn toàn sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="192.0.2.100" drop'

# Reload sudo firewall-cmd --reload

Xem tất cả rich rules đang active:

sudo firewall-cmd --list-rich-rules

Giới hạn tần suất với firewalld

Tương tự ufw limit, firewalld dùng rich rules để giới hạn connection rate:

# Giới hạn SSH: tối đa 10 connections/phút
sudo firewall-cmd --permanent --add-rich-rule='rule service name="ssh" accept limit value="10/m"'

# Giới hạn HTTP: tối đa 25 connections/giây (chống request flood) sudo firewall-cmd --permanent --add-rich-rule='rule service name="http" accept limit value="25/s"'

sudo firewall-cmd --reload

Các đơn vị thời gian có thể dùng: /s (giây), /m (phút), /h (giờ), /d (ngày).

Giới hạn tần suất ở tầng firewall chỉ là lớp bảo vệ đầu tiên. Với web server, bạn nên kết hợp thêm rate limit ở tầng Nginx (limit_req_zone) hoặc application level để bảo vệ đa lớp.

IP whitelist và blacklist

Trong môi trường production, có những IP bạn luôn muốn cho phép (office, VPN gateway) và những IP cần chặn vĩnh viễn.

UFW

# Whitelist — cho phép mọi traffic từ office IP
sudo ufw allow from 203.0.113.50

# Whitelist — cho phép SSH chỉ từ IP cụ thể sudo ufw allow from 203.0.113.50 to any port 22

# Blacklist — chặn hoàn toàn một IP sudo ufw deny from 192.0.2.100

# Blacklist — chặn cả subnet sudo ufw deny from 198.51.100.0/24

Thứ tự rules quan trọng trong UFW. Rules được đánh số và xử lý từ trên xuống. Nếu muốn chắc chắn rule chặn được xử lý trước:

# Insert rule vào vị trí đầu tiên
sudo ufw insert 1 deny from 192.0.2.100

firewalld

# Whitelist — thêm IP vào zone trusted (cho phép mọi thứ)
sudo firewall-cmd --permanent --zone=trusted --add-source=203.0.113.50/32
sudo firewall-cmd --reload
# Blacklist — thêm IP vào zone drop (drop mọi thứ, không response)
sudo firewall-cmd --permanent --zone=drop --add-source=192.0.2.100/32
sudo firewall-cmd --reload

Zone trusted cho phép mọi traffic không cần filter. Zone drop thì drop tất cả packet mà không gửi response (kẻ tấn công không biết server có tồn tại hay không).

Geo-blocking, chặn traffic theo quốc gia

Nếu server của bạn chỉ phục vụ người dùng Việt Nam, không có lý do gì phải chấp nhận SSH connection từ một IP ở Nga hay Trung Quốc. Geo-blocking giúp giảm đáng kể bề mặt tấn công.

Cách làm dùng ipset kết hợp iptables:

# Cài ipset
# Ubuntu/Debian
sudo apt install ipset
# AlmaLinux/Rocky
sudo dnf install ipset

Tải danh sách IP theo quốc gia từ ipdeny.com và tạo ipset:

# Tạo ipset cho quốc gia cần chặn (ví dụ: CN — Trung Quốc)
sudo ipset create geoblock-cn hash:net

# Tải zone file và import wget -q https://www.ipdeny.com/ipblocks/data/countries/cn.zone -O /tmp/cn.zone

# Thêm từng dải IP vào ipset while read cidr; do sudo ipset add geoblock-cn "$cidr" done < /tmp/cn.zone

# Tạo iptables rule chặn ipset này sudo iptables -I INPUT -m set --match-set geoblock-cn src -j DROP

Để chặn nhiều quốc gia, lặp lại với country code khác (ru, kp, ir...). Bạn có thể gộp chung vào một ipset nếu muốn.

ipset rules sẽ mất sau khi reboot. Để persist, dùng ipset save > /etc/ipset.conf và tạo systemd service load lại lúc boot. Hoặc đơn giản hơn, cho script chạy trong cron @reboot.

Lưu ipset và tạo script auto-load:

# Lưu ipset hiện tại
sudo ipset save > /etc/ipset.conf
# Tạo script restore khi boot
cat << 'EOF' | sudo tee /etc/NetworkManager/dispatcher.d/01-ipset
#!/bin/bash
if [ "$1" = "lo" ] && [ "$2" = "up" ]; then
  ipset restore < /etc/ipset.conf
fi
EOF
sudo chmod +x /etc/NetworkManager/dispatcher.d/01-ipset

Port knocking, ẩn port SSH

Port knocking là kỹ thuật ẩn port hoàn toàn cho đến khi client gửi đúng "chuỗi gõ cửa". Ví dụ: bạn phải kết nối lần lượt đến port 7000, 8000, 9000 (theo đúng thứ tự), sau đó port 22 mới mở ra cho IP của bạn.

Với ai chưa hình dung: giống như phải bấm đúng mật mã ở cổng trước khi cửa mở. Nmap scan cũng không thấy port 22 tồn tại.

Cài knockd:

# Ubuntu/Debian
sudo apt install knockd
# AlmaLinux/Rocky
sudo dnf install epel-release
sudo dnf install knock-server

Cấu hình /etc/knockd.conf:

[options]
    UseSyslog

[openSSH] sequence = 7000,8000,9000 seq_timeout = 10 command = /usr/sbin/ufw allow from %IP% to any port 22 tcpflags = syn

[closeSSH] sequence = 9000,8000,7000 seq_timeout = 10 command = /usr/sbin/ufw delete allow from %IP% to any port 22 tcpflags = syn

Giải thích:

  • [openSSH]: khi client gõ lần lượt port 7000 → 8000 → 9000 trong vòng 10 giây, UFW sẽ mở port 22 cho IP đó.
  • [closeSSH]: gõ ngược lại (9000 → 8000 → 7000) sẽ đóng port 22 lại.

Bật knockd:

# Sửa file default (Ubuntu)
sudo nano /etc/default/knockd
# Đổi START_KNOCKD=0 thành START_KNOCKD=1
# Set KNOCKD_OPTS="-i eth0" (đổi eth0 thành interface của bạn)
sudo systemctl enable knockd
sudo systemctl start knockd

Từ client, "gõ cửa" bằng lệnh knock:

# Cài knock client (macOS: brew install knock, Linux: apt install knockd)
knock server-ip 7000 8000 9000

# Giờ SSH vào bình thường ssh user@server-ip

# Xong việc, đóng lại knock server-ip 9000 8000 7000

Port knocking thêm một lớp bảo vệ nhưng không nên là lớp duy nhất. Kết hợp với SSH key authentication và fail2ban để có defense in depth. Ngoài ra, hãy chắc chắn bạn có backup access (console/VNC) phòng trường hợp knockd gặp sự cố.

Logging, theo dõi traffic bị chặn

Firewall chặn request là tốt, nhưng nếu không log lại thì bạn không biết đang bị tấn công kiểu gì, từ đâu, và mức độ ra sao.

UFW logging

# Bật logging
sudo ufw logging on
# Hoặc set mức log chi tiết hơn
sudo ufw logging high

Các mức log: off, low, medium, high, full. Mức medium hoặc high là đủ cho production. Mức full log mọi thứ, có thể gây đầy disk nhanh.

Xem log:

# UFW log nằm trong syslog hoặc file riêng
sudo tail -f /var/log/ufw.log
# Hoặc dùng journalctl
sudo journalctl -u ufw -f

firewalld logging

# Log tất cả packet bị denied
sudo firewall-cmd --set-log-denied=all
# Hoặc chỉ log unicast (bỏ qua broadcast/multicast noise)
sudo firewall-cmd --set-log-denied=unicast

Xem log:

# firewalld log vào kernel log
sudo journalctl -k | grep -i "reject\|drop"
# Hoặc dùng dmesg
sudo dmesg | grep -i "reject\|drop"

Bạn cũng có thể log traffic được accept bằng rich rule, hữu ích khi cần audit:

# Log mọi SSH connection được accept
sudo firewall-cmd --permanent --add-rich-rule='rule service name="ssh" log prefix="SSH_ACCESS: " level="info" accept'
sudo firewall-cmd --reload

Fail-safe rules, đừng tự lock mình ra ngoài

Đây là bài học đau thương mà nhiều sysadmin học bằng cách... mất quyền truy cập server. Khi cấu hình firewall, luôn đảm bảo:

1. SSH luôn mở trước khi bật firewall

# UFW — LUÔN chạy lệnh này TRƯỚC khi enable
sudo ufw allow ssh
sudo ufw enable
# firewalld — SSH thường đã mở mặc định, nhưng kiểm tra cho chắc
sudo firewall-cmd --list-services | grep ssh

2. Dùng cron job tự tắt firewall

Khi đang thử nghiệm rules mới, set một cron job tự disable firewall sau 5 phút. Nếu rules mới lock bạn ra, chỉ cần đợi 5 phút là server mở lại:

# Tạo "dead man's switch" — tự tắt UFW sau 5 phút
echo "ufw disable" | sudo at now + 5 minutes
# Hoặc với crontab (1 lần)
echo "$(date -d '+5 minutes' '+%M %H %d %m *') root /usr/sbin/ufw disable" | sudo tee /etc/cron.d/ufw-failsafe

Nếu mọi thứ hoạt động ổn, xóa cron job đó đi. Nếu bạn bị lock out, nó sẽ cứu bạn.

3. Luôn có backup access

  • Console access: hầu hết VPS provider đều có VNC hoặc web console. Đây là cách vào server khi SSH bị block.
  • Out-of-band management: nếu dùng dedicated server, đảm bảo IPMI/iDRAC/iLO đang hoạt động.
  • Second SSH port: mở thêm SSH trên port khác (ví dụ 2222) với rule allow vĩnh viễn, phòng trường hợp rule port 22 bị lỗi.
# Mở SSH backup port
sudo ufw allow 2222/tcp comment 'Backup SSH'
# Hoặc firewalld
sudo firewall-cmd --permanent --add-port=2222/tcp
sudo firewall-cmd --reload

Ví dụ production: Web server hoàn chỉnh

Tổng hợp mọi thứ lại, đây là setup firewall cho một web server production điển hình. Server chạy Nginx + MySQL + Docker, phục vụ website.

UFW (Ubuntu)

# Reset UFW (cẩn thận — sẽ xóa hết rules hiện tại)
sudo ufw reset

# Default policy: chặn hết incoming, cho phép outgoing sudo ufw default deny incoming sudo ufw default allow outgoing

# SSH với rate limit sudo ufw limit ssh/tcp comment 'SSH rate limited'

# Backup SSH port sudo ufw allow 2222/tcp comment 'Backup SSH'

# HTTP/HTTPS sudo ufw allow 80/tcp comment 'HTTP' sudo ufw allow 443/tcp comment 'HTTPS'

# MySQL — chỉ từ localhost (app kết nối local) # Không cần rule UFW vì MySQL bind 127.0.0.1

# Whitelist office IP sudo ufw allow from 203.0.113.50 comment 'Office IP'

# Bật logging sudo ufw logging medium

# Bật firewall sudo ufw enable

# Xem tổng quan sudo ufw status numbered

Tiếp theo, xử lý Docker. Thêm vào /etc/ufw/after.rules:

*filter
:DOCKER-USER - [0:0]
-A DOCKER-USER -s 10.0.0.0/8 -j ACCEPT
-A DOCKER-USER -s 172.16.0.0/12 -j ACCEPT
-A DOCKER-USER -s 192.168.0.0/16 -j ACCEPT
-A DOCKER-USER -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
-A DOCKER-USER -p tcp --dport 80 -j ACCEPT
-A DOCKER-USER -p tcp --dport 443 -j ACCEPT
-A DOCKER-USER -j DROP
COMMIT

Thêm anti-DDoS rules vào /etc/ufw/before.rules (trước dòng COMMIT):

# SYN flood protection
-A ufw-before-input -p tcp --syn -m limit --limit 12/s --limit-burst 24 -j ACCEPT
-A ufw-before-input -p tcp --syn -j DROP
# Drop invalid packets
-A ufw-before-input -m conntrack --ctstate INVALID -j DROP

Reload tất cả:

sudo ufw reload

firewalld (AlmaLinux)

# Set default zone
sudo firewall-cmd --set-default-zone=public

# Mở HTTP/HTTPS sudo firewall-cmd --permanent --add-service=http sudo firewall-cmd --permanent --add-service=https

# SSH rate limit sudo firewall-cmd --permanent --add-rich-rule='rule service name="ssh" accept limit value="10/m"'

# Backup SSH port sudo firewall-cmd --permanent --add-port=2222/tcp

# Tạo zone cho internal services sudo firewall-cmd --permanent --new-zone=internal-db sudo firewall-cmd --permanent --zone=internal-db --add-source=10.0.0.0/8 sudo firewall-cmd --permanent --zone=internal-db --add-service=mysql

# Whitelist office IP sudo firewall-cmd --permanent --zone=trusted --add-source=203.0.113.50/32

# Log denied traffic sudo firewall-cmd --set-log-denied=unicast

# Reload sudo firewall-cmd --reload

# Kiểm tra sudo firewall-cmd --list-all sudo firewall-cmd --list-all --zone=internal-db sudo firewall-cmd --list-all --zone=trusted

Checkpoint: kiểm tra setup của bạn

Sau khi cấu hình xong, chạy qua checklist này để đảm bảo mọi thứ hoạt động đúng:

1. Kiểm tra rules đang active

# UFW
sudo ufw status verbose
# firewalld
sudo firewall-cmd --list-all
sudo firewall-cmd --list-all-zones | grep -A 10 "active"

2. Test rate limit SSH

Từ một máy khác, thử kết nối SSH liên tục nhanh:

# Chạy từ máy client — thử 10 connection liên tiếp
for i in $(seq 1 10); do
  ssh -o ConnectTimeout=2 user@server-ip exit 2>/dev/null
  echo "Attempt $i: exit code $?"
done

Những lần đầu sẽ thành công (exit code 0), sau vài lần sẽ bị từ chối (exit code khác 0). Đó là rate limit đang hoạt động.

3. Verify Docker không bypass firewall

# Chạy một container test expose port 9999
docker run -d --name test-bypass -p 9999:80 nginx

# Từ máy khác, thử kết nối curl -m 5 http://server-ip:9999

# Nếu nhận được response → Docker đang bypass firewall → cần fix # Nếu timeout → firewall chặn đúng

# Dọn dẹp docker rm -f test-bypass

4. Kiểm tra port mở từ bên ngoài

# Từ máy khác hoặc dùng online tool
nmap -Pn server-ip
# Chỉ nên thấy: 22 (SSH), 80 (HTTP), 443 (HTTPS)
# Và 2222 nếu có backup SSH

5. Kiểm tra logging hoạt động

# UFW
sudo tail -5 /var/log/ufw.log
# firewalld
sudo journalctl -k --since "5 minutes ago" | grep -i "reject\|drop"

Nếu tất cả check đều pass, bạn đã có một production firewall setup vững chắc. Kết hợp thêm fail2ban (sẽ có ở bài khác trong serie) và bạn sẽ có một lớp phòng thủ rất khó bị xuyên thủng.

Chia sẻ:
Bài viết đã được kiểm duyệt bởi AZDIGI Team

Về tác giả

Trần Thắng

Trần Thắng

Chuyên gia tại AZDIGI với nhiều năm kinh nghiệm trong lĩnh vực web hosting và quản trị hệ thống.

Hơn 10 năm phục vụ 80.000+ khách hàng

Bắt đầu dự án web của bạn với AZDIGI