❤️ 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.
Khi bạn thuê một VPS và đưa lên internet, nó giống như đặt một căn nhà giữa ngã tư đông đúc vậy. Ai cũng có thể đi ngang, gõ cửa, thậm chí thử mở khóa. Firewall chính là cánh cổng giúp bạn kiểm soát ai được vào, ai bị chặn lại.

Bài này mình sẽ đi qua 2 công cụ firewall phổ biến nhất trên Linux VPS: UFW cho Ubuntu/Debian và firewalld cho AlmaLinux/Rocky/RHEL. Cả hai đều là frontend của iptables/nftables phía dưới, nhưng cách dùng khác nhau hoàn toàn.
Firewall là gì và tại sao VPS cần nó?
Firewall (tường lửa) là lớp bảo vệ nằm giữa VPS của bạn và internet. Nhiệm vụ của nó đơn giản: kiểm tra mọi kết nối đến và đi, rồi quyết định cho phép hay chặn dựa trên các rule bạn đặt ra.
Một VPS mới tinh thường mở toang tất cả các port. Nghĩa là nếu bạn chạy MySQL trên port 3306, ai trên internet cũng có thể thử kết nối vào. Bot quét port chạy 24/7, và VPS của bạn sẽ bị “gõ cửa” liên tục chỉ vài phút sau khi online.
Firewall giúp bạn:
- Chỉ mở đúng port cần thiết (SSH, HTTP, HTTPS)
- Chặn truy cập vào các service nội bộ (database, cache, admin panel)
- Giới hạn truy cập theo IP hoặc subnet
- Giảm bề mặt tấn công đáng kể
Trên Linux, iptables (và nftables ở kernel mới) là engine xử lý firewall thực sự. Nhưng viết rule iptables trực tiếp khá phức tạp, nên người ta tạo ra các tool frontend dễ dùng hơn. Ubuntu/Debian dùng UFW, còn RHEL-based (AlmaLinux, Rocky, CentOS) dùng firewalld.
UFW trên Ubuntu/Debian
UFW viết tắt của Uncomplicated Firewall. Đúng như tên gọi, nó được thiết kế để đơn giản nhất có thể. Trên Ubuntu, UFW được cài sẵn nhưng mặc định tắt.
Kiểm tra trạng thái
sudo ufw status
Nếu chưa bật, bạn sẽ thấy Status: inactive. Khi đã bật và có rule, nó sẽ hiện danh sách các rule đang active.
Muốn xem chi tiết hơn với output dạng verbose:
sudo ufw status verbose
Thiết lập default policy
Trước khi bật UFW, bạn nên set default policy. Nguyên tắc là: chặn hết traffic đến, cho phép hết traffic đi ra.
sudo ufw default deny incoming
sudo ufw default allow outgoing
Với policy này, mọi kết nối từ bên ngoài vào sẽ bị chặn trừ khi bạn cho phép rõ ràng. Còn VPS vẫn kết nối ra ngoài bình thường (cập nhật package, gửi email, gọi API,…).
Bật UFW
⚠️ Luôn allow SSH trước khi bật UFW! Nếu bạn bật firewall mà chưa mở port 22, bạn sẽ bị lock out khỏi VPS ngay lập tức. Lúc đó phải vào console từ panel nhà cung cấp để sửa.
sudo ufw allow 22/tcp
sudo ufw enable
UFW sẽ hỏi xác nhận vì nó có thể làm gián đoạn kết nối SSH hiện tại. Gõ y để tiếp tục. Thực tế nếu bạn đã allow port 22 thì kết nối hiện tại không bị ảnh hưởng.
Các lệnh quản lý rule
Mở port cho service:
# Mở HTTP
sudo ufw allow 80/tcp
# Mở HTTPS
sudo ufw allow 443/tcp
# Mở cả HTTP và HTTPS cùng lúc
sudo ufw allow 80,443/tcp
Cho phép truy cập từ IP hoặc subnet cụ thể:
# Cho phép một IP cụ thể truy cập tất cả port
sudo ufw allow from 203.0.113.50
# Cho phép cả subnet
sudo ufw allow from 192.168.1.0/24
# Cho phép một IP chỉ truy cập port cụ thể
sudo ufw allow from 203.0.113.50 to any port 3306
Chặn port:
# Chặn MySQL từ bên ngoài
sudo ufw deny 3306
# Chặn một IP cụ thể
sudo ufw deny from 198.51.100.0/24
Xem rule có đánh số:
sudo ufw status numbered
Output sẽ kiểu như:
Status: active
To Action From
-- ------ ----
[ 1] 22/tcp ALLOW IN Anywhere
[ 2] 80/tcp ALLOW IN Anywhere
[ 3] 443/tcp ALLOW IN Anywhere
[ 4] 3306 DENY IN Anywhere
Xóa rule:
# Xóa theo nội dung rule
sudo ufw delete allow 80/tcp
# Xóa theo số thứ tự (lấy từ status numbered)
sudo ufw delete 2
Reset toàn bộ:
sudo ufw reset
Lệnh này xóa sạch tất cả rule và tắt UFW. Nó sẽ backup rule cũ vào file trước khi xóa, nên không sợ mất hoàn toàn.
UFW với application profile
UFW có sẵn một số application profile. Xem danh sách bằng:
sudo ufw app list
Bạn có thể allow theo tên app thay vì port number:
sudo ufw allow 'Nginx Full'
sudo ufw allow 'OpenSSH'
Tiện hơn so với nhớ port number, nhất là với các service dùng nhiều port.
firewalld trên AlmaLinux/Rocky/RHEL
firewalld là firewall manager mặc định trên các distro RHEL-based. Khác với UFW dùng kiểu “allow port X, deny port Y” đơn giản, firewalld tổ chức rule theo zone. Mỗi zone là một bộ rule áp dụng cho network interface hoặc source IP.
Kiểm tra trạng thái
# Xem firewalld có đang chạy không
sudo firewall-cmd --state
# Bật firewalld
sudo systemctl start firewalld
# Bật firewalld khi khởi động
sudo systemctl enable firewalld
Xem toàn bộ cấu hình hiện tại:
sudo firewall-cmd --list-all
Output mẫu:
public (active)
target: default
icmp-block-inversion: no
interfaces: eth0
sources:
services: cockpit dhcpv6-client ssh
ports:
protocols:
forward: yes
masquerade: no
forward-ports:
source-ports:
icmp-blocks:
rich rules:
Khái niệm Zone
Zone là điểm khác biệt lớn nhất giữa firewalld và UFW. Thay vì rule áp dụng chung cho cả server, firewalld cho phép bạn gán mỗi network interface hoặc source IP vào một zone khác nhau, và mỗi zone có bộ rule riêng.
Các zone quan trọng nhất:
- public (mặc định): Dùng cho mạng không tin tưởng. Chỉ cho phép các service bạn chỉ định. Đây là zone bạn sẽ dùng nhiều nhất trên VPS.
- trusted: Cho phép mọi kết nối. Dùng cho mạng nội bộ mà bạn tin tưởng hoàn toàn (ví dụ: VPN internal).
- drop: Chặn mọi kết nối đến mà không phản hồi gì. Kẻ tấn công không biết port có tồn tại hay không. Dùng khi muốn “giấu” VPS.
- internal: Tương tự trusted nhưng cho mạng nội bộ cụ thể.
- dmz: Cho các server cần expose ra ngoài nhưng giới hạn quyền truy cập vào mạng nội bộ.
Xem zone đang active:
sudo firewall-cmd --get-active-zones
Xem danh sách tất cả zone có sẵn:
sudo firewall-cmd --get-zones
Quản lý rule với firewall-cmd
firewalld có 2 loại config: runtime (có hiệu lực ngay, mất khi restart) và permanent (lưu vĩnh viễn, cần reload để áp dụng). Luôn thêm --permanent rồi --reload để rule không bị mất.
Mở service:
# Mở HTTP
sudo firewall-cmd --add-service=http --permanent
# Mở HTTPS
sudo firewall-cmd --add-service=https --permanent
# Apply thay đổi
sudo firewall-cmd --reload
firewalld có sẵn định nghĩa cho rất nhiều service (http, https, ssh, mysql, postgresql,…). Xem danh sách đầy đủ:
sudo firewall-cmd --get-services
Mở port cụ thể:
# Mở port 8080
sudo firewall-cmd --add-port=8080/tcp --permanent
# Mở range port
sudo firewall-cmd --add-port=3000-3100/tcp --permanent
sudo firewall-cmd --reload
Xóa rule:
# Xóa service
sudo firewall-cmd --remove-service=http --permanent
# Xóa port
sudo firewall-cmd --remove-port=8080/tcp --permanent
sudo firewall-cmd --reload
Giới hạn truy cập theo IP (rich rule):
# Cho phép IP cụ thể truy cập port 3306
sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="203.0.113.50" port port="3306" protocol="tcp" accept'
# Chặn một IP
sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="198.51.100.0/24" drop'
sudo firewall-cmd --reload
Gán source IP vào zone:
# Đưa subnet nội bộ vào zone trusted
sudo firewall-cmd --permanent --zone=trusted --add-source=10.0.0.0/8
sudo firewall-cmd --reload
So sánh UFW và firewalld
| Tiêu chí | UFW | firewalld |
|---|---|---|
| Distro mặc định | Ubuntu, Debian | AlmaLinux, Rocky, RHEL, Fedora |
| Độ phức tạp | Đơn giản, dễ học | Phức tạp hơn, nhiều khái niệm |
| Cách tổ chức rule | Flat list (allow/deny) | Zone-based |
| Runtime vs Permanent | Mọi thay đổi lưu ngay | Cần --permanent + --reload |
| Service profiles | Application profile đơn giản | Service definitions chi tiết |
| Rich rules | Hạn chế | Hỗ trợ tốt (rich rule syntax) |
| Phù hợp với | VPS đơn giản, web server | Hệ thống phức tạp, nhiều network zone |
| Backend | iptables/nftables | nftables (mới) / iptables (cũ) |
Nói ngắn gọn: UFW đơn giản và đủ dùng cho hầu hết VPS. firewalld mạnh hơn nếu bạn cần quản lý nhiều zone, nhiều network interface, hoặc rule phức tạp.
Setup firewall cho VPS mới (step-by-step)
Đây là quy trình mình recommend cho bất kỳ VPS mới nào. Thứ tự các bước rất quan trọng, đặc biệt bước 1.
Trên Ubuntu (UFW)
# Bước 1: Allow SSH TRƯỚC — bước này bắt buộc
sudo ufw allow 22/tcp
# Bước 2: Set default policy
sudo ufw default deny incoming
sudo ufw default allow outgoing
# Bước 3: Bật firewall
sudo ufw enable
# Bước 4: Mở HTTP/HTTPS (nếu chạy web server)
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
# Bước 5: Verify
sudo ufw status verbose
Output sau bước 5 nên trông như thế này:
Status: active
Logging: on (low)
Default: deny (incoming), allow (outgoing), disabled (routed)
New profiles: skip
To Action From
-- ------ ----
22/tcp ALLOW IN Anywhere
80/tcp ALLOW IN Anywhere
443/tcp ALLOW IN Anywhere
Trên AlmaLinux (firewalld)
# Bước 1: Kiểm tra SSH đã được allow chưa (thường có sẵn)
sudo firewall-cmd --list-services
# Nếu không thấy "ssh" trong danh sách:
sudo firewall-cmd --add-service=ssh --permanent
# Bước 2: Bật firewalld
sudo systemctl start firewalld
sudo systemctl enable firewalld
# Bước 3: Mở HTTP/HTTPS
sudo firewall-cmd --add-service=http --permanent
sudo firewall-cmd --add-service=https --permanent
# Bước 4: Default zone "public" đã deny mọi thứ ngoài service được allow
# Không cần set thêm gì
# Bước 5: Apply và verify
sudo firewall-cmd --reload
sudo firewall-cmd --list-all
Output sau bước 5:
public (active)
target: default
interfaces: eth0
services: dhcpv6-client http https ssh
ports:
...
Trên AlmaLinux/Rocky, firewalld thường được bật sẵn và SSH đã được allow. Nhưng luôn kiểm tra trước khi thêm rule, đừng assume.
Ví dụ thực tế
Web server cơ bản
VPS chạy Nginx/Apache, chỉ cần mở 3 port: SSH (22), HTTP (80), HTTPS (443).
UFW:
sudo ufw allow 22/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
firewalld:
sudo firewall-cmd --add-service=ssh --permanent
sudo firewall-cmd --add-service=http --permanent
sudo firewall-cmd --add-service=https --permanent
sudo firewall-cmd --reload
Database server: chặn MySQL từ bên ngoài
MySQL chạy trên port 3306. Bạn muốn chỉ có app server (IP 10.0.0.5) mới kết nối được, chặn hết phần còn lại.
UFW:
# Chặn 3306 từ tất cả
sudo ufw deny 3306
# Cho phép riêng app server
sudo ufw allow from 10.0.0.5 to any port 3306
firewalld:
# Dùng rich rule để chỉ cho phép IP cụ thể
sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="10.0.0.5" port port="3306" protocol="tcp" accept'
sudo firewall-cmd --reload
# Không add service mysql vào zone public — mặc định đã bị chặn rồi
Lưu ý: MySQL mặc định bind trên 127.0.0.1 (chỉ localhost). Nếu bạn thay đổi bind-address trong config MySQL thì mới cần lo chuyện firewall cho port 3306.
Docker + Firewall: cái bẫy mà nhiều người gặp
⚠️ Docker bypass UFW! Đây là lỗi rất phổ biến. Docker tự thêm rule vào iptables, bỏ qua hoàn toàn UFW. Nghĩa là dù bạn deny port 3306 trong UFW, nếu có container expose port 3306, nó vẫn accessible từ internet.
Khi bạn chạy docker run -p 3306:3306 mysql, Docker sẽ tạo rule iptables riêng trong chain DOCKER, nằm ngoài tầm kiểm soát của UFW. Nhiều người tưởng firewall đang chặn nhưng thực tế port vẫn mở toang.
Cách xử lý trên Ubuntu (UFW):
Tạo hoặc sửa file /etc/docker/daemon.json:
{
"iptables": false
}
Sau đó restart Docker:
sudo systemctl restart docker
Tuy nhiên, cách này có side effect: Docker container sẽ không tự tạo NAT rule, bạn phải tự quản lý networking. Cách khác mềm dẻo hơn là dùng chain DOCKER-USER trong iptables:
# Chặn truy cập từ ngoài vào container port 3306
sudo iptables -I DOCKER-USER -i eth0 -p tcp --dport 3306 -j DROP
# Cho phép IP cụ thể
sudo iptables -I DOCKER-USER -i eth0 -s 10.0.0.5 -p tcp --dport 3306 -j ACCEPT
Nhớ save iptables rule để không mất khi reboot:
sudo apt install iptables-persistent
sudo netfilter-persistent save
Trên AlmaLinux (firewalld):
firewalld và Docker cũng có xung đột tương tự, nhưng Docker từ version 20.10+ đã hỗ trợ firewalld tốt hơn. Nếu bạn dùng Docker trên AlmaLinux, đảm bảo Docker service start sau firewalld và kiểm tra bằng firewall-cmd --list-all sau khi start container.
Cách an toàn nhất cho cả hai distro: không expose port ra host nếu không cần. Dùng Docker network internal và để container giao tiếp với nhau qua network name thay vì publish port.
# Thay vì: docker run -p 3306:3306 mysql
# Dùng Docker network:
docker network create app-net
docker run --network app-net --name db mysql
docker run --network app-net --name app myapp
# app kết nối MySQL qua hostname "db", không cần expose port
Lỗi thường gặp
1. Quên allow SSH rồi bật firewall
Đây là lỗi kinh điển. Bạn bật UFW hoặc firewalld mà chưa mở port 22, kết nối SSH hiện tại có thể vẫn sống (vì nó đã established), nhưng khi disconnect là không vào lại được.
Cách khắc phục: Vào VPS console qua web panel của nhà cung cấp (noVNC, IPMI, hoặc tương tự). Từ đó chạy:
# UFW
sudo ufw allow 22/tcp
# Hoặc nếu muốn reset hết
sudo ufw disable
# firewalld
sudo firewall-cmd --add-service=ssh --permanent
sudo firewall-cmd --reload
Phòng ngừa: Luôn mở SSH trước khi làm bất kỳ thứ gì khác với firewall. Tạo thói quen.
2. firewalld: dùng –permanent nhưng quên –reload
Bạn thêm rule với --permanent, tưởng nó đã hoạt động, nhưng thực tế chưa. Rule permanent chỉ lưu vào config file, chưa apply vào runtime. Phải chạy firewall-cmd --reload để nó có hiệu lực.
# SAI: rule chưa hoạt động
sudo firewall-cmd --add-service=http --permanent
# ĐÚNG: thêm rule + apply
sudo firewall-cmd --add-service=http --permanent
sudo firewall-cmd --reload
Ngược lại, nếu bạn thêm rule không có --permanent, nó hoạt động ngay nhưng sẽ mất khi restart firewalld hoặc reboot server.
3. Docker bypass UFW (đã nói ở trên)
Nhắc lại vì nó quan trọng: nếu bạn dùng Docker trên Ubuntu, đừng tin UFW là đủ. Kiểm tra bằng cách scan port từ bên ngoài để xác nhận. Không có gì thay thế được việc verify thực tế.
4. Mở quá nhiều port “cho chắc”
Một số người mở sẵn port 8080, 8443, 3000, 9090,… “phòng khi cần”. Nguyên tắc là: chỉ mở port nào đang thực sự dùng. Khi nào cần thêm thì mở, dùng xong thì đóng. Ít port mở = ít rủi ro.
Checkpoint: Verify firewall đang hoạt động
Setup xong rồi thì phải verify. Đừng tin lệnh status là đủ, hãy kiểm tra từ bên ngoài.
Kiểm tra port đang mở trên server
# Xem port nào đang listen
sudo ss -tlnp
Output cho bạn biết service nào đang chạy trên port nào. Nhưng đây chỉ là góc nhìn từ bên trong server.
Scan port từ bên ngoài bằng nmap
Từ máy local hoặc một VPS khác, chạy nmap để xem bên ngoài thấy gì:
# Scan các port phổ biến
nmap your-server-ip
# Scan port cụ thể
nmap -p 22,80,443,3306 your-server-ip
# Scan tất cả 65535 port (lâu hơn)
nmap -p- your-server-ip
Kết quả mong đợi cho web server cơ bản:
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
443/tcp open https
3306/tcp filtered mysql
open nghĩa là port đang accessible. filtered nghĩa là firewall đang chặn (không phản hồi). closed nghĩa là port không có service nào listen nhưng firewall không chặn.
Test thử truy cập port bị chặn
# Từ máy khác, thử kết nối port 3306
nc -zv your-server-ip 3306
# Hoặc dùng telnet
telnet your-server-ip 3306
Nếu firewall hoạt động đúng, kết nối sẽ timeout hoặc bị refused. Nếu nó connect được thì bạn cần kiểm tra lại rule.
Verify firewall status
# UFW
sudo ufw status verbose
# firewalld
sudo firewall-cmd --list-all
sudo firewall-cmd --list-ports
sudo firewall-cmd --list-services
Kết hợp cả ss (bên trong) và nmap (bên ngoài) cho bạn bức tranh đầy đủ: service nào đang chạy, port nào đang mở, và firewall có thực sự chặn những gì cần chặn không.
📝 Bài tập checkpoint: Setup firewall trên VPS của bạn theo hướng dẫn ở trên. Sau đó từ máy local, dùng nmap scan server và so sánh kết quả với rule đã đặt. Thử tắt firewall, scan lại, rồi bật lại để thấy sự khác biệt. Đó là cách tốt nhất để hiểu firewall đang làm gì.
Có thể bạn cần xem thêm
- Firewall nâng cao trên Linux VPS - UFW và firewalld cho production
- Checklist bảo mật VPS Linux - 15 bước thiết yếu
- Hướng dẫn cài đặt cấu hình UFW trên Ubuntu/Debian
- Troubleshooting VPS Linux - Cách xủ lý sự cố VPS phổ biến
- Networking cơ bản trên Linux VPS - IP, DNS, curl và wget
- Bật Remote MySQL trên DirectAdmin
Về tác giả
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.