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:
- Passive mode IP sai: Server nằm sau NAT (VPS thường vậy),
pasv_addressphải là public IP — không phải private IP của interface. Lấy IP public bằngcurl ifconfig.me. - 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.

