vsftpd 作為 Linux 下安全性最高的 FTP 服務器之一,結合 MySQL 存儲虛擬用戶信息,通過 PAM 模塊實現認證授權,可構建無系統賬號、目錄隔離、配額控制的多租戶 FTP 服務。本文記錄 Debian/Ubuntu 環境下完整部署流程。
FTP Client → vsftpd (PAM) → libpam-mysql → MySQL (ftpu.user 表)
↓
本地系統用戶 ftpguest (映射虛擬用戶權限)
↓
文件系統目錄 /home/ftp/{user}/ (虛擬用戶獨立根目錄)
apt-get update && apt-get install -y vsftpd
# 創建虛擬用戶映射的本地系統用戶 (無登錄shell、無家目錄)
useradd -r -s /usr/sbin/nologin -d /var/empty ftpguest
# 創建 FTP 根目錄並授權
mkdir -p /home/ftp
chown ftpguest:nogroup /home/ftp
chmod 755 /home/ftp
apt-get install -y mysql-server mysql-client
# 安全初始化
mysql_secure_installation
# 創建 FTP 虛擬用戶庫表
mysql -u root -p -e "
CREATE DATABASE ftpu CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE ftpu;
CREATE TABLE \`user\` (
\`id\` INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
\`username\` VARCHAR(50) NOT NULL UNIQUE COMMENT 'FTP用戶名',
\`password\` CHAR(41) NOT NULL COMMENT 'PASSWORD()哈希值',
\`homedir\` VARCHAR(255) NOT NULL DEFAULT '/' COMMENT '相對FTP根目錄的家目錄',
\`enable\` TINYINT(1) NOT NULL DEFAULT 1 COMMENT '1啟用 0禁用',
\`quota_size\` BIGINT UNSIGNED DEFAULT 0 COMMENT '磁盤配額(bytes), 0不限制',
\`quota_files\` INT UNSIGNED DEFAULT 0 COMMENT '文件數配額, 0不限制',
\`create_time\` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
\`update_time\` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_enable (enable)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='FTP虛擬用戶表';
-- 插入測試用戶 (密碼: TestPass123!)
INSERT INTO \`user\` (username, password, homedir) VALUES
('test1', PASSWORD('TestPass123!'), '/test1'),
('test2', PASSWORD('TestPass123!'), '/test2');
-- 創建 PAM 專用讀取用戶
CREATE USER 'pam_ftp'@'localhost' IDENTIFIED BY 'PamFtpPass123!';
GRANT SELECT ON ftpu.user TO 'pam_ftp'@'localhost';
FLUSH PRIVILEGES;
"
apt-get install -y libpam-mysql
# 配置 vsftpd PAM 服務
cat > /etc/pam.d/vsftpd << 'EOF'
#%PAM-1.0
auth required pam_mysql.so \
user=pam_ftp \
passwd=PamFtpPass123! \
host=localhost \
db=ftpu \
table=user \
usercolumn=username \
passwdcolumn=password \
crypt=3 \
where="enable=1"
account required pam_mysql.so \
user=pam_ftp \
passwd=PamFtpPass123! \
host=localhost \
db=ftpu \
table=user \
usercolumn=username \
passwdcolumn=password \
crypt=3 \
where="enable=1"
EOF
關鍵參數:crypt=3 表示 MySQL PASSWORD() 哈希 (8.0+ 為 caching_sha2_password,需確認兼容性,建議應用層自行驗證或用明文對比方案)
cat > /etc/vsftpd.conf << 'EOF'
# 基礎運行
listen=YES
listen_ipv6=NO
anonymous_enable=NO
local_enable=YES
# 虛擬用戶核心配置
guest_enable=YES
guest_username=ftpguest
virtual_use_local_privs=YES
# 寫入權限
write_enable=YES
local_umask=022
anon_upload_enable=NO
anon_mkdir_write_enable=NO
anon_other_write_enable=NO
# 目錄限制 (關鍵:虛擬用戶見不到彼此目錄)
chroot_local_user=YES
chroot_list_enable=NO
allow_writeable_chroot=YES
# 用戶配置目錄 (實現每用戶獨立根目錄)
user_config_dir=/etc/vsftpd_user_conf
# 日誌
xferlog_enable=YES
xferlog_file=/var/log/vsftpd.log
xferlog_std_format=YES
dual_log_enable=YES
vsftpd_log_file=/var/log/vsftpd_xfer.log
# 性能與安全
idle_session_timeout=300
data_connection_timeout=120
max_clients=200
max_per_ip=20
pasv_enable=YES
pasv_min_port=40000
pasv_max_port=40100
pasv_address=YOUR_PUBLIC_IP # 被動模式公網IP
EOF
# 創建用戶配置目錄
mkdir -p /etc/vsftpd_user_conf
# 為每個虛擬用戶創建配置文件 (文件名 = 用戶名)
cat > /etc/vsftpd_user_conf/test1 << 'EOF'
local_root=/home/ftp/test1
EOF
cat > /etc/vsftpd_user_conf/test2 << 'EOF'
local_root=/home/ftp/test2
EOF
# 創建實際目錄並授權
mkdir -p /home/ftp/test1 /home/ftp/test2
chown -R ftpguest:nogroup /home/ftp/test1 /home/ftp/test2
chmod 755 /home/ftp/test1 /home/ftp/test2
# 可選:設置磁盤配額 (需文件系統支持 quota)
# setquota -u ftpguest 100M 200M 0 0 /home/ftp (項目配額)
# 重啟 vsftpd
systemctl restart vsftpd
systemctl enable vsftpd
# 防火牆放行
ufw allow 20/tcp
ufw allow 21/tcp
ufw allow 40000:40100/tcp
ufw reload
# 測試連接 (命令行 FTP 客戶端)
ftp localhost
# Name: test1
# Password: TestPass123!
# ftp> ls
# ftp> put local_file.txt
# ftp> get remote_file.txt
# ftp> quit
# 或使用 FileZilla / WinSCP 圖形化客戶端測試
#!/bin/bash
# manage_ftp_user.sh
# 用法: ./manage_ftp_user.sh add|del|disable|enable [password] [homedir]
ACTION=$1
USER=$2
PASS=$3
HOMEDIR=${4:-"/$USER"}
case $ACTION in
add)
mysql -u root -p${MYSQL_ROOT_PASS} -e "
USE ftpu;
INSERT INTO \`user\` (username, password, homedir) VALUES ('$USER', PASSWORD('$PASS'), '$HOMEDIR');
"
mkdir -p /home/ftp$HOMEDIR
chown ftpguest:nogroup /home/ftp$HOMEDIR
cat > /etc/vsftpd_user_conf/$USER << EOF
local_root=/home/ftp$HOMEDIR
EOF
echo "User $USER added"
;;
del)
mysql -u root -p${MYSQL_ROOT_PASS} -e "USE ftpu; DELETE FROM \`user\` WHERE username='$USER';"
rm -f /etc/vsftpd_user_conf/$USER
# 可選刪除目錄: rm -rf /home/ftp$HOMEDIR
echo "User $USER deleted"
;;
disable)
mysql -u root -p${MYSQL_ROOT_PASS} -e "USE ftpu; UPDATE \`user\` SET enable=0 WHERE username='$USER';"
echo "User $USER disabled"
;;
enable)
mysql -u root -p${MYSQL_ROOT_PASS} -e "USE ftpu; UPDATE \`user\` SET enable=1 WHERE username='$USER';"
echo "User $USER enabled"
;;
*)
echo "Usage: $0 add|del|disable|enable [password] [homedir]"
;;
esac
| 現象 | 原因 | 解決 |
|---|---|---|
| 530 Login incorrect | PAM認證失敗:密碼哈希不匹配/用戶禁用/where條件 | 檢查 /var/log/auth.log;驗證 crypt 參數;確認 enable=1;MySQL 8.0+ PASSWORD() 已移除,需應用層驗證 |
| 500 OOPS: cannot change directory | local_root 目錄不存在/權限錯誤 | 確認目錄存在、ftpguest 擁有權、chroot 允許寫入 |
| 被動模式無法傳輸 | pasv_port_range 未放防火牆 / pasv_address 錯誤 | 防火牆放行 40000-40100;pasv_address 設置公網IP |
| 虛擬用戶能看到根目錄其他用戶文件夾 | chroot_local_user=NO 或 user_config_dir 未生效 | 確認 chroot_local_user=YES;user_config_dir 路徑正確;配置文件名=用戶名 |
MySQL 8.0 移除了 PASSWORD() 函數,caching_sha2_password 為默認認證插件。libpam-mysql 的 crypt=3 可能不兼容。解決方案:
SHA2(CONCAT('salt', password), 256) 或 bcrypt 哈希,PAM 配置 crypt=0 (明文對比,需應用層在 PAM 對話中驗證,較複雜)ALTER USER 'ftp_user'@'%' IDENTIFIED WITH mysql_native_password BY 'pass'; (僅限兼容場景)pam_exec.so 調用 Python/Go 腳本查庫驗證),完全脫離 PAM-MySQL 限制