Hướng dẫn cài đặt và cấu hình FTP Server bảo mật với vsftpd trên CentOS Stream 9: Hỗ trợ SSL/TLS và ảo hóa người dùng

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

FTP server — nghe có vẻ cổ điển, nhưng thực tế mình vẫn gặp yêu cầu này khá thường xuyên. Hồi trước công ty mình vẫn còn vài con server chạy CentOS 7, và lúc migrate sang AlmaLinux mình mới để ý: có không ít phòng ban vẫn đang dùng FTP thuần để transfer file nội bộ. Vậy là phải setup lại toàn bộ, lần này mình chọn vsftpd trên CentOS Stream 9 và làm đàng hoàng hơn — có SSL/TLS, có virtual users.

So sánh các approach khi setup FTP Server

Trước khi bắt tay làm, mình cân nhắc ba hướng chính:

1. FTP thuần (plain FTP) với system users

Cách đơn giản nhất — mỗi user FTP là một user thật trên hệ thống Linux. Thêm user vào /etc/passwd là xong. Nhược điểm rõ ràng: user FTP có thể SSH vào server nếu bạn không khóa shell. Ngoài ra, credential đi qua mạng hoàn toàn plaintext — không phù hợp với môi trường production.

2. SFTP (SSH File Transfer Protocol)

Nhiều người nhầm SFTP với FTPS (FTP over SSL). SFTP chạy trên port 22, dùng luôn SSH protocol. Bảo mật tốt, không cần cài thêm gì nếu đã có OpenSSH. Nhưng một số legacy client hoặc thiết bị nhúng — NAS cũ, PLC công nghiệp — chỉ hiểu FTP thuần, không hỗ trợ SFTP.

3. vsftpd với SSL/TLS (FTPS) + Virtual Users

Mình chọn cách này cho production. vsftpd (Very Secure FTP Daemon) nhẹ, nhanh, và có track record bảo mật đáng nể — từng là FTP server chính thức của ftp.kernel.org. Kết hợp SSL/TLS để mã hóa kết nối + virtual users (user ảo, không phải system user) để cô lập hoàn toàn FTP khỏi tài khoản hệ thống. Setup phức tạp hơn một chút, nhưng xứng đáng.

Phân tích ưu nhược điểm

Approach Ưu điểm Nhược điểm
Plain FTP + system users Đơn giản, nhanh Không mã hóa, rủi ro bảo mật cao
SFTP Bảo mật tốt, không cần cài thêm Client phải hỗ trợ SSH, không phải FTP thật
vsftpd + SSL/TLS + virtual users Bảo mật cao, linh hoạt, cô lập user Setup phức tạp hơn, cần cấu hình firewall + SELinux

Chọn cách phù hợp

Với môi trường nội bộ và client kiểm soát được (đồng nghiệp dùng FileZilla, WinSCP), SFTP thừa sức và đơn giản hơn nhiều. Nhưng nếu:

  • Client là thiết bị legacy chỉ hỗ trợ FTP/FTPS
  • Cần nhiều user FTP với quyền hạn khác nhau mà không muốn tạo system account
  • Cần audit trail, chroot từng user vào thư mục riêng

…thì vsftpd với virtual users là lựa chọn hợp lý. Mình đi theo hướng này.

Hướng dẫn triển khai

Bước 1: Cài đặt vsftpd

sudo dnf install -y vsftpd
sudo systemctl enable vsftpd
sudo systemctl start vsftpd

Bước 2: Tạo chứng chỉ SSL tự ký

Với môi trường nội bộ, cert tự ký là đủ. Nếu server public, dùng Let’s Encrypt thay thế.

sudo mkdir -p /etc/vsftpd/ssl
sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
  -keyout /etc/vsftpd/ssl/vsftpd.key \
  -out /etc/vsftpd/ssl/vsftpd.crt \
  -subj "/C=VN/ST=HCM/L=HoChiMinh/O=MyOrg/CN=ftp.example.com"

sudo chmod 600 /etc/vsftpd/ssl/vsftpd.key
sudo chmod 644 /etc/vsftpd/ssl/vsftpd.crt

Bước 3: Cấu hình vsftpd

Backup file config gốc rồi tạo config mới:

sudo cp /etc/vsftpd/vsftpd.conf /etc/vsftpd/vsftpd.conf.bak
sudo tee /etc/vsftpd/vsftpd.conf << 'EOF'
# --- Cơ bản ---
anonymous_enable=NO
local_enable=YES
write_enable=YES
local_umask=022
dirmessage_enable=YES
xferlog_enable=YES
xferlog_std_format=YES
xferlog_file=/var/log/vsftpd.log

# --- Chroot ---
chroot_local_user=YES
allow_writeable_chroot=YES

# --- Passive mode (quan trọng khi có NAT/firewall) ---
pasv_enable=YES
pasv_min_port=40000
pasv_max_port=40100
pasv_address=YOUR_SERVER_IP

# --- SSL/TLS ---
ssl_enable=YES
force_local_data_ssl=YES
force_local_logins_ssl=YES
ssl_tlsv1_2=YES
ssl_sslv2=NO
ssl_sslv3=NO
rsa_cert_file=/etc/vsftpd/ssl/vsftpd.crt
rsa_private_key_file=/etc/vsftpd/ssl/vsftpd.key

# --- Virtual users ---
guest_enable=YES
guest_username=ftpuser
virtual_use_local_privs=YES
pam_service_name=vsftpd_virtual
user_config_dir=/etc/vsftpd/vusers_conf

# --- Listen ---
listen=YES
listen_ipv6=NO
listen_port=21
EOF

Thay YOUR_SERVER_IP bằng IP thật của server. Dải 40000-40100 cho phép 101 kết nối passive đồng thời — với môi trường nội bộ ít người, thu hẹp xuống 40000-40020 cũng đủ, tránh mở quá nhiều port trên firewall.

Bước 4: Tạo virtual users với PAM + BerkeleyDB

vsftpd dùng PAM để xác thực virtual users, cụ thể là module pam_userdb đọc file BerkeleyDB. Cài libdb-utils để có tool tạo DB:

sudo dnf install -y libdb-utils

Tạo file danh sách user (format: username / password xen kẽ):

sudo tee /etc/vsftpd/vusers_passwd << 'EOF'
uploader
Mat_Khau_Manh_1!
reader
Mat_Khau_Manh_2!
EOF

Convert sang BerkeleyDB:

sudo db_load -T -t hash -f /etc/vsftpd/vusers_passwd /etc/vsftpd/vusers.db
sudo chmod 600 /etc/vsftpd/vusers.db
sudo rm /etc/vsftpd/vusers_passwd  # Xóa file plaintext sau khi tạo DB

Tạo system user ftpuser (không có login shell, chỉ dùng làm guest account cho vsftpd):

sudo useradd -d /srv/ftp -s /sbin/nologin ftpuser
sudo mkdir -p /srv/ftp
sudo chown ftpuser:ftpuser /srv/ftp

Bước 5: Cấu hình PAM cho virtual users

sudo tee /etc/pam.d/vsftpd_virtual << 'EOF'
auth required pam_userdb.so db=/etc/vsftpd/vusers
account required pam_userdb.so db=/etc/vsftpd/vusers
EOF

Bước 6: Cấu hình per-user (tùy chọn)

Tính năng hay nhất của vsftpd — mỗi virtual user có thư mục home riêng và quyền khác nhau, không cần thêm bất kỳ system account nào:

sudo mkdir -p /etc/vsftpd/vusers_conf

# Config cho user 'uploader'
sudo tee /etc/vsftpd/vusers_conf/uploader << 'EOF'
local_root=/srv/ftp/uploader
write_enable=YES
EOF

# Config cho user 'reader' (chỉ đọc)
sudo tee /etc/vsftpd/vusers_conf/reader << 'EOF'
local_root=/srv/ftp/reader
write_enable=NO
EOF

# Tạo thư mục và phân quyền
sudo mkdir -p /srv/ftp/uploader /srv/ftp/reader
sudo chown ftpuser:ftpuser /srv/ftp/uploader /srv/ftp/reader

Bước 7: Mở firewall

# Mở port FTP control + passive range
sudo firewall-cmd --permanent --add-port=21/tcp
sudo firewall-cmd --permanent --add-port=40000-40100/tcp
sudo firewall-cmd --reload

# Kiểm tra
sudo firewall-cmd --list-ports

Bước 8: Cấu hình SELinux

Đây là bước nhiều người hay bỏ qua rồi đứng debug mãi không ra. SELinux trên CentOS Stream 9 mặc định ở chế độ Enforcing — cần thêm một số boolean và context:

# Cho phép vsftpd đọc/ghi home directory của virtual users
sudo setsebool -P ftp_home_dir on
sudo setsebool -P ftpd_full_access on

# Nếu dùng thư mục tùy chỉnh ngoài /var/ftp
sudo semanage fcontext -a -t public_content_rw_t "/srv/ftp(/.*)?" 2>/dev/null || \
  sudo chcon -R -t public_content_rw_t /srv/ftp

sudo restorecon -Rv /srv/ftp

Nếu chưa có semanage:

sudo dnf install -y policycoreutils-python-utils

Bước 9: Khởi động lại và kiểm tra

sudo systemctl restart vsftpd
sudo systemctl status vsftpd

# Kiểm tra vsftpd đang listen
sudo ss -tlnp | grep :21

Test kết nối với FileZilla

Mở FileZilla, chọn File → Site Manager → New Site:

  • Protocol: FTP – File Transfer Protocol
  • Encryption: Require explicit FTP over TLS
  • Host: IP server
  • Port: 21
  • Logon Type: Normal
  • User/Password: uploader / Mat_Khau_Manh_1!

Lần đầu kết nối, FileZilla sẽ hỏi xác nhận self-signed cert — chấp nhận là được. Nếu thấy lỗi 425 Failed to establish connection, gần như chắc chắn là lỗi passive mode: kiểm tra lại pasv_address và đảm bảo firewall đã mở dải port 40000-40100.

Một vài lưu ý từ thực tế

Mình hay gặp hai vấn đề phổ biến khi triển khai:

  1. Passive mode IP sai: Server nằm sau NAT (VPS thường vậy), pasv_address phải là public IP — không phải private IP của interface. Lấy IP public bằng curl ifconfig.me.
  2. SELinux block ngầm: Login được nhưng transfer file thì treo? Chạy sudo ausearch -m avc -ts recent | grep vsftpd — có output là SELinux đang chặn.

Thêm virtual user mới sau này? Cập nhật file text, chạy lại db_load, tạo thư mục tương ứng là xong. Không cần đụng system user hay restart vsftpd — daemon đọc per-user config theo từng session mới.

Share: