Cấu hình Firewall với iptables trên Linux: Script thực chiến cho web server production

Linux tutorial - IT technology blog
Linux tutorial - IT technology blog

Server mở toang mà không ai biết

Tuần trước mình review lại một server Ubuntu của khách hàng — đang chạy web app PHP kèm MySQL. Chạy thử nmap -sV <ip> từ ngoài internet, kết quả hiện ra đủ thứ: cổng 3306 (MySQL), 6379 (Redis), 8080 (dev server)… tất cả mở ra ngoài. Không có firewall nào chặn cả.

Câu chuyện quen thuộc lắm. Server chạy ngon, không có cảnh báo gì, nên firewall cứ bị đẩy xuống dưới todo list mãi. Cho đến khi MySQL bị brute-force hoặc Redis bị dùng để chạy cryptominer — lúc đó mới tá hỏa.

Tại sao server lại “mở” dù không ai cố tình để vậy?

Khi cài MySQL, Redis, hay bất kỳ dịch vụ nào, chúng mặc định bind vào 0.0.0.0 — lắng nghe trên tất cả network interface, kể cả interface kết nối internet. Nếu không có firewall chặn, bất kỳ ai cũng có thể thử kết nối vào.

Linux kernel có sẵn netfilter — framework lọc gói tin ở tầng kernel. iptables là công cụ dòng lệnh để cấu hình netfilter. Mặc định sau khi cài Ubuntu hay CentOS mới, iptables thường ở trạng thái ACCEPT tất cả — không chặn gì.

Vấn đề nữa: nhiều admin dùng ufw (Ubuntu) hoặc firewalld (CentOS/RHEL) như một hộp đen — biết lệnh, không hiểu bên dưới. Khi cần NAT, port forwarding, hay rate limiting nâng cao, các wrapper này bắt đầu bộc lộ giới hạn. Hiểu iptables trực tiếp giúp bạn debug đúng chỗ khi gặp trường hợp đó.

Hiểu cấu trúc iptables trước khi bắt tay

iptables tổ chức rules theo tableschains. Table dùng nhiều nhất là filter với 3 chains:

  • INPUT: packet đi vào server này
  • OUTPUT: packet từ server này đi ra ngoài
  • FORWARD: packet đi qua server (dùng khi server làm router/gateway)

Mỗi rule có một target quyết định số phận gói tin:

  • ACCEPT — cho đi qua
  • DROP — chặn im lặng, không thông báo
  • REJECT — chặn và gửi lại thông báo lỗi
  • LOG — ghi log rồi tiếp tục xử lý rule tiếp theo

Rules được đánh giá theo thứ tự từ trên xuống. Rule nào khớp đầu tiên sẽ được áp dụng — các rule phía sau không chạy nữa.

ufw hay iptables trực tiếp?

Cách 1: Dùng ufw — đơn giản nhưng giới hạn

Đủ dùng cho server nhỏ chỉ cần mở vài port cố định:

sudo ufw enable
sudo ufw allow 22/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw status verbose

Ưu điểm: nhanh, dễ nhớ. Nhược điểm: khó xử lý NAT, rate limiting, hay các rule nâng cao. Khi gặp trường hợp đặc biệt, bạn vẫn phải quay về iptables.

Cách 2: Cấu hình iptables trực tiếp — kiểm soát toàn phần

Đây là cách mình dùng cho server production. Phức tạp hơn một chút nhưng cho phép kiểm soát từng chi tiết.

Script iptables hoàn chỉnh cho web server production

Thay vì gõ từng lệnh rời rạc rồi không nhớ đã làm gì, mình viết một script để dễ quản lý và deploy lại khi cần. Trên server Ubuntu 22.04 mình đang quản lý, mỗi giờ có vài trăm kết nối bot bị chặn — không có firewall thì tất cả cái đó đều đến tay ứng dụng xử lý.

#!/bin/bash
# firewall.sh — iptables config cho web server

# Xóa tất cả rules cũ
iptables -F
iptables -X
iptables -Z

# Default policy: DROP tất cả INPUT và FORWARD
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT ACCEPT

# Cho phép loopback (localhost — bắt buộc phải có)
iptables -A INPUT -i lo -j ACCEPT

# Stateful firewall: cho phép kết nối đã established
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

# Cho phép SSH (PHẢI có trước khi set DROP policy)
iptables -A INPUT -p tcp --dport 22 -j ACCEPT

# Cho phép HTTP và HTTPS
iptables -A INPUT -p tcp --dport 80 -j ACCEPT
iptables -A INPUT -p tcp --dport 443 -j ACCEPT

# Cho phép ping
iptables -A INPUT -p icmp --icmp-type echo-request -j ACCEPT

# Log các packet bị DROP để debug
iptables -A INPUT -j LOG --log-prefix "IPT-DROP: " --log-level 4
iptables -A INPUT -j DROP

echo "Firewall configured!"
iptables -L -v --line-numbers

Cảnh báo quan trọng: Nếu đang SSH vào server, rule --dport 22 phải được thêm trước khi set default policy DROP. Thứ tự sai là tự khóa mình ra ngoài ngay lập tức.

Mẹo an toàn khi test lần đầu: đặt lệnh tự reset sau 5 phút, phòng trường hợp lỡ tay tự khoá SSH:

echo "iptables -F && iptables -P INPUT ACCEPT" | at now + 5 minutes

(Cần cài at trước nếu chưa có: sudo apt install at)

Chống brute-force SSH bằng rate limiting

Thay vì ACCEPT tất cả kết nối SSH, thêm rate limiting để block bot quét mật khẩu:

# Thay rule SSH đơn giản ở trên bằng:
iptables -A INPUT -p tcp --dport 22 -m conntrack --ctstate NEW \
  -m recent --set --name SSH
iptables -A INPUT -p tcp --dport 22 -m conntrack --ctstate NEW \
  -m recent --update --seconds 60 --hitcount 4 --name SSH -j DROP
iptables -A INPUT -p tcp --dport 22 -j ACCEPT

Rule này chặn IP nào thử kết nối SSH mới quá 4 lần trong 60 giây.

Khóa MySQL và Redis — chỉ cho localhost hoặc IP nội bộ

# MySQL: chỉ cho phép từ localhost
iptables -A INPUT -p tcp --dport 3306 -s 127.0.0.1 -j ACCEPT

# Hoặc từ dải IP nội bộ (VPC, private network)
iptables -A INPUT -p tcp --dport 3306 -s 10.0.0.0/8 -j ACCEPT

# Redis: tương tự
iptables -A INPUT -p tcp --dport 6379 -s 127.0.0.1 -j ACCEPT

# Chặn tất cả còn lại cho 2 port này
iptables -A INPUT -p tcp --dport 3306 -j DROP
iptables -A INPUT -p tcp --dport 6379 -j DROP

Lưu rules để tự load khi reboot

Rules iptables mất sau reboot nếu không lưu lại — đây là điểm nhiều người hay quên:

# Ubuntu/Debian
sudo apt install iptables-persistent
sudo netfilter-persistent save

# Kiểm tra file đã lưu
cat /etc/iptables/rules.v4

# CentOS/RHEL 7
sudo service iptables save
# Rules được lưu tại /etc/sysconfig/iptables

Các lệnh kiểm tra và debug hay dùng

# Xem tất cả rules với số thứ tự và packet/byte count
iptables -L -v --line-numbers

# Xem rules theo table cụ thể
iptables -t nat -L -v

# Xóa rule theo số thứ tự (ví dụ xóa rule số 3 trong INPUT)
iptables -D INPUT 3

# Xem log bị DROP
dmesg | grep "IPT-DROP"
# hoặc
tail -f /var/log/kern.log | grep "IPT-DROP"

# Reset toàn bộ về mặc định (khi muốn bắt đầu lại)
iptables -F && iptables -P INPUT ACCEPT && iptables -P FORWARD ACCEPT

Các bẫy kinh điển khi cấu hình iptables

  • Tự khóa SSH: Luôn test trong tmux/screen, kết hợp lệnh reset tự động như mẹo ở trên.
  • Rules mất sau reboot: Nhớ cài iptables-persistent và chạy netfilter-persistent save.
  • Conflict với ufw/firewalld: Nếu đang dùng ufw, tắt trước khi dùng iptables trực tiếp: sudo ufw disable.
  • Docker bypass firewall: Docker tự thêm rules vào iptables và có thể vô hiệu hóa FORWARD policy của bạn. Nếu chạy Docker, cần cấu hình thêm DOCKER-USER chain.
  • Thiếu rule ESTABLISHED: Nếu quên dòng --ctstate ESTABLISHED,RELATED, các kết nối đã mở sẽ bị DROP — website sẽ load xong header rồi treo.

Tổng kết

iptables không phải việc cài xong là xong. Mỗi khi thêm service mới, cần tự hỏi: port này có cần public ra ngoài không? Mặc định là không. Script template ở trên đủ dùng cho hầu hết web server — thêm bớt rule tùy ứng dụng cụ thể.

Nguyên tắc mình hay áp dụng khi nhận server mới: chạy nmap -sV <ip> từ ngoài trước. Port nào hiện ra mà không có lý do tồn tại — đóng ngay. MySQL, Redis, Elasticsearch, Memcached đều chỉ nên nghe từ localhost hoặc internal network.

Share: