Hướng dẫn quản lý dịch vụ với firewalld trên CentOS/RHEL

CentOS tutorial - IT technology blog
CentOS tutorial - IT technology blog

Làm quen với firewalld trong 5 phút

Lần đầu mình đụng vào firewalld là khi chuyển từ Ubuntu sang CentOS 7 để quản lý server production. Quen với ufw rồi thấy firewalld ban đầu khá lạ. Khái niệm “zone” nghe phức tạp. Nhưng sau vài tháng dùng thật, mình nhận ra nó tiện hơn iptables thuần nhiều — đặc biệt khi server có 2–3 NIC cần policy khác nhau.

Đây là 4 lệnh cần chạy ngay:

# Kiểm tra trạng thái firewalld
sudo systemctl status firewalld

# Bật firewalld và cho phép khởi động cùng hệ thống
sudo systemctl enable --now firewalld

# Xem zone hiện tại và interface đang gắn vào
sudo firewall-cmd --get-active-zones

# Xem toàn bộ rule đang áp dụng
sudo firewall-cmd --list-all

Chạy xong 4 lệnh này là bạn đã nắm được tình trạng firewall của server. Zone mặc định thường là public, và output của --list-all cho thấy service nào đang được mở.

Mở port hoặc service ngay lập tức

# Cho phép HTTP và HTTPS
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https

# Mở port tùy chỉnh (ví dụ: Node.js app chạy port 3000)
sudo firewall-cmd --permanent --add-port=3000/tcp

# Áp dụng thay đổi — bắt buộc sau khi thêm rule permanent
sudo firewall-cmd --reload

Chú ý cờ --permanent: thiếu cờ này, rule chỉ sống đến lần reboot tiếp theo. Còn --reload kích hoạt rule permanent ngay lập tức — không cần restart toàn bộ service.

Hiểu về Zone — Trái tim của firewalld

Zone là thứ làm firewalld khác hoàn toàn so với iptables thuần. Thay vì viết rule theo chain (INPUT/OUTPUT/FORWARD), bạn gán interface vào zone — mỗi zone có policy riêng, quản lý độc lập.

Các zone mặc định:

  • drop — Từ chối tất cả, không gửi phản hồi
  • block — Từ chối tất cả, gửi ICMP rejection
  • public — Zone mặc định, chỉ tin tưởng một số service cụ thể
  • external — Interface ra ngoài, có NAT masquerade
  • internal — Mạng nội bộ, tin tưởng hơn public
  • trusted — Cho phép toàn bộ traffic
  • dmz — Khu vực DMZ, hạn chế inbound
# Xem danh sách tất cả zone
sudo firewall-cmd --get-zones

# Thay đổi zone mặc định
sudo firewall-cmd --set-default-zone=public

# Gán interface vào zone cụ thể (ví dụ: NIC nội bộ)
sudo firewall-cmd --permanent --zone=internal --add-interface=eth1
sudo firewall-cmd --reload

Quản lý service định nghĩa sẵn

firewalld đi kèm hơn 60 service định nghĩa sẵn, lưu tại /usr/lib/firewalld/services/. Mỗi file XML khai báo port và protocol tương ứng — tiện hơn nhiều so với nhớ số port thủ công.

# Xem danh sách service có sẵn
sudo firewall-cmd --get-services

# Thêm/xóa service
sudo firewall-cmd --permanent --add-service=mysql
sudo firewall-cmd --permanent --remove-service=telnet

# Xem service nào đang được phép trong zone public
sudo firewall-cmd --zone=public --list-services

Nếu service bạn cần chưa có định nghĩa sẵn, tạo file XML riêng:

<?xml version="1.0" encoding="utf-8"?>
<service>
  <short>MyApp</short>
  <description>Custom application on port 8080</description>
  <port protocol="tcp" port="8080"/>
</service>
# Lưu file vào /etc/firewalld/services/myapp.xml rồi reload
sudo firewall-cmd --reload
sudo firewall-cmd --permanent --add-service=myapp
sudo firewall-cmd --reload

Nâng cao: Rich Rules và Port Forwarding

Rich Rules — Kiểm soát chi tiết hơn

Sau vụ CentOS 8 EOL, mình phải migrate gấp 5 server sang Rocky Linux trong 1 tuần. Đó là lần đầu mình thật sự đọc kỹ về rich rules — có mấy server cần whitelist IP cụ thể cho SSH, mà --add-source đơn thuần không đủ linh hoạt.

# Chỉ cho phép SSH từ IP cụ thể
sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="192.168.1.10" service name="ssh" accept'

# Chặn hẳn một IP
sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="10.0.0.5" reject'

# Giới hạn rate SSH để chống brute force (tối đa 5 kết nối/phút)
sudo firewall-cmd --permanent --add-rich-rule='rule service name="ssh" limit value="5/m" accept'

# Log traffic trước khi drop (debug hoặc audit)
sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="203.0.113.0/24" service name="http" log prefix="HTTP-BLOCK" level="warning" drop'

sudo firewall-cmd --reload

Port Forwarding

App chạy cổng 8080 nhưng muốn người dùng vào cổng 80 mà không cần dựng thêm Nginx chỉ để forward? Port forwarding giải quyết gọn:

# Forward port 80 sang 8080 trên cùng máy
sudo firewall-cmd --permanent --add-forward-port=port=80:proto=tcp:toport=8080

# Forward sang máy khác trong mạng nội bộ
sudo firewall-cmd --permanent --add-forward-port=port=3306:proto=tcp:toaddr=192.168.1.20:toport=3306

sudo firewall-cmd --reload

Tips thực tế từ production

1. Test tạm thời trước khi commit permanent

Mình học bài này theo cách đau đớn: test firewall rule mới, quên thêm --permanent, rule tưởng đã lưu nhưng reboot xong thì mất. May mà server đó không critical lắm.

# Bước 1: Test không permanent — rule mất sau reboot
sudo firewall-cmd --add-service=https
# Kiểm tra kết nối xem hoạt động chưa

# Bước 2: Nếu ổn thì mới lưu permanent
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --reload

2. Backup config trước khi thay đổi lớn

# Config firewalld lưu tại /etc/firewalld/zones/
ls /etc/firewalld/zones/

# Backup toàn bộ config với timestamp
sudo cp -r /etc/firewalld /etc/firewalld.bak.$(date +%Y%m%d)

# Restore nếu cần
sudo cp -r /etc/firewalld.bak.20240115/* /etc/firewalld/
sudo firewall-cmd --reload

3. Debug khi rule không hoạt động như kỳ vọng

# So sánh runtime vs permanent (hai trạng thái này hay lệch nhau)
sudo firewall-cmd --list-all            # runtime
sudo firewall-cmd --list-all --permanent  # permanent

# Xem log firewalld
sudo journalctl -u firewalld -f

# Panic mode — chặn tất cả ngay (dùng khi đang bị tấn công)
sudo firewall-cmd --panic-on
sudo firewall-cmd --panic-off

4. Kết hợp với SELinux — đừng quên lớp này

Firewalld và SELinux là hai lớp bảo mật độc lập hoàn toàn. Mở port trên firewalld không tự động cho phép service bind vào port đó nếu SELinux đang block. Mình từng mất 30 phút debug không hiểu sao firewall đã mở mà vẫn không vào được — cuối cùng mới nhớ ra SELinux. Từ đó về sau, gặp network issue là check cả hai ngay từ đầu:

# Ví dụ: nginx chạy port 8080 cần cả hai bước

# 1. Mở firewalld
sudo firewall-cmd --permanent --add-port=8080/tcp

# 2. Cho phép SELinux (nếu đang enforcing)
sudo semanage port -a -t http_port_t -p tcp 8080

sudo firewall-cmd --reload

5. Script sync firewall cho nhiều server

5 server chạy config firewall khác nhau là thảm họa khi audit. Sau đợt migrate Rocky Linux xong, mình viết luôn script nhỏ để đảm bảo mọi server đều có rule đồng nhất:

#!/bin/bash
# sync-firewall.sh — Apply firewall rules đồng nhất trên nhiều server

SERVERS=("server1.example.com" "server2.example.com" "server3.example.com")
RULES=(
  "--add-service=http"
  "--add-service=https"
  "--add-service=ssh"
  "--add-port=8080/tcp"
)

for server in "${SERVERS[@]}"; do
  echo "Configuring $server..."
  for rule in "${RULES[@]}"; do
    ssh root@$server "firewall-cmd --permanent $rule"
  done
  ssh root@$server "firewall-cmd --reload"
  echo "Done: $server"
done

Ba thứ cần nhớ khi dùng firewalld: zone quyết định policy, --permanent mới thật sự lưu, và firewalld chỉ là một lớp — SELinux vẫn có quyền phủ quyết phía trên. Sau đợt migrate 5 server CentOS 8 sang Rocky Linux, config firewalld chuyển qua không một dòng phải sửa.

Share: