
上一篇我们在 CentOS Stream 10 上把 vsftpd 装好、让 FTP 服务能正常启停了。但你如果真拿系统里的真实账号去登录一次多半会发现这条路不太好走密码跟 SSH 登录是同一个权限全靠系统目录去卡用户想上传个文件你还得斟酌半天该不该给写权限一个账号一旦泄露等于把系统登录凭证也搭了进去。所以生产环境里我几乎不用系统本地账号直登 FTP这套用的都是虚拟用户访问。虚拟用户说白了就是给 FTP 单独设计的账号体系。它只活在 vsftpd 的认证库里跟 /etc/passwd 没有任何关系密码再长再乱也不会牵连系统登录多用户之间的目录隔离、权限控制也能做得更干净。这篇文章就接着上一篇往下走把 CentOS Stream 10 vsftpd 虚拟用户配置整个流程过一遍包含账号规划、Berkeley DB 认证库、PAM 模块调用、SELinux 与防火墙放行最后把我这几年来遇到的各种报错情况整理成一份排查速查表照着敲基本上不会卡壳。1. 为什么不用本地用户三种认证模式的实际对比1.1 匿名、本地用户、虚拟用户到底差了些什么FTP 认证模式翻来覆去就三种匿名、本地用户、虚拟用户。很多新手上来默认用了本地用户方案就是因为在 vsftpd.conf 里看到local_enableYES以为这是唯一选项实际上这是最容易埋雷的做法。匿名模式anonymous_enableYES不需要账号密码适合公共资源共享下载。它的所有访问都映射到 ftp 或 anonymous 用户权限结构最简单基本不考虑多用户隔离。本地用户模式local_enableYES直接用系统账号登录好处是账号管理跟系统统一缺点是密码、家目录、登录权限全部耦合在一起。你想给 A 一个 FTP 账号就等于给他一个能登录系统的真实凭证哪怕把 shell 设成 nologin心里的不踏实感还是消不掉。虚拟用户模式guest_enableYES账号密码存在独立的数据库里跟系统用户完全脱钩。认证通过后由 vsftpd 把本次会话统一映射到一个指定的系统用户身份上权限力度再通过独立配置细化。一句话概括匿名是开门迎客本地用户是拿家门钥匙迎客虚拟用户是给客人发单独的临时门卡。1.2 vsftpd 的 guest 机制到底是怎么工作的虚拟用户这套方案核心是 vsftpd 的 guest 机制理解它之后排查问题会顺手很多。FTP 客户端发起登录提交用户名密码。vsftpd 把认证请求抛给 PAM 模块PAM 再调用 pam_userdb.so 去查指定的 Berkeley DB 数据库文件。数据库里账号密码匹配成功PAM 返回通过。vsftpd 将会话身份切换到 guest_username 指定的系统用户比如 vftp。后续所有文件读写、目录遍历都以 vftp 这个系统身份去执行。这个设计最妙的地方在于虚拟用户只存在于 FTP 认证体系里就算密码被人看光了别人也拿它登录不了系统因为没有对应的系统账号。而且多个虚拟用户最后共享一个系统身份不会出现 /etc/passwd 里账号膨胀的问题真要给每个人单独目录和权限再通过 user_config_dir 机制做差异化配置就行。2. 动手前的准备环境确认与基础安装2.1 先确认系统状态别急着装包虽然标题写的是 CentOS Stream 10但我仍然建议先看一眼系统版本和防火墙状态尤其是手头有多台服务器的情况下cat /etc/redhat-release dnf repolist | head -20CentOS Stream 10 的仓库默认就带 vsftpd正常执行安装就能拉到包。另外确认一下 SELinux 当前的模式我见过不少新手直接把 SELinux 关掉来排除问题这是最不推荐的做法后面会专门讲怎么正确放行。getenforce如果是 Enforcing照常往下走别关。2.2 安装 vsftpd 和数据库工具虚拟用户认证需要使用 Berkeley DB 格式的数据库文件创建数据库依赖 db_load 命令这个命令在 libdb-utils 包里。很多人只装了 vsftpd结果卡在这一步dnf install -y vsftpd libdb-utils装完后顺手确认一下服务是否存在systemctl list-unit-files | grep vsftpd2.3 规划账号目录结构动手之前先想清楚三件事映射到哪个系统用户、虚拟用户的根目录放哪、独立配置目录放哪。我习惯这样规划映射用户vftpshell 设置为 /sbin/nologin主目录 /data/ftp虚拟用户示例user01、user02独立配置目录/etc/vsftpd/vuser_conf这一步先规划好后面能少改好几次配置。规划映射用户时目录不一定要放在 /var/ftp放在独立的 /data/ftp 更清晰也方便挂载独立磁盘或做存储扩容。3. 搭建虚拟用户认证体系从文本到 Berkeley DB3.1 创建映射用的系统用户虚拟用户最终要落到一个系统身份上所以先建这个幕后用户useradd -s /sbin/nologin -d /data/ftp vftp mkdir -p /data/ftp chown vftp:vftp /data/ftp-s /sbin/nologin这一项很关键相当于给这个账号上了锁防止它被用于 SSH 登录。如果你用的是 CentOS Stream 10 或类 Red Hat 系系统注意检查一下 /sbin/nologin 是否存在于 /etc/shells否则有些 PAM 模块会较真。3.2 创建虚拟用户密码文件在 /etc/vsftpd 目录下建一个纯文本文件用来管理虚拟用户账号密码。格式非常简单一行用户名、一行密码交替排列不要加任何多余符号vim /etc/vsftpd/vusers.txt内容示例user01 Pssw0rd_2025 user02 Pssw0rd_2026这里有两个经验之谈。第一密码不要用弱口令因为 FTP 流量是明文传输密码在网络上是裸奔的弱口令等于把门锁挂在大街上。第二这个文本文件在生成数据库之后就不是必需品了但建议保留备份放在一个权限严格限制的位置方便以后维护账号毕竟改数据库文件没有改文本文件直观。3.3 用 db_load 生成认证数据库文本文件不能直接被 PAM 读取需要用 db_load 转换成 Berkeley DB 格式db_load -T -t hash -f /etc/vsftpd/vusers.txt /etc/vsftpd/vusers.db chmod 600 /etc/vsftpd/vusers.db这里解释一下参数的含义理解了以后出问题你会知道去哪找原因-T告诉 db_load 输入是普通文本文件text file-t hash指定数据库底层用 hash 结构存储vsftpd 的 PAM 认证默认推荐这种结构-f后面跟输入文本文件路径最后一个参数是输出的数据库文件路径权限也值得较真。数据库文件里存的是密码如果权限太宽松别的系统用户就能读走所以 600 是最低标准。文本源文件同理建议也改成 600。3.4 修改 PAM 配置文件PAM 是 Linux 下的统一认证框架vsftpd 通过 /etc/pam.d/vsftpd 来指定认证方式。修改之前先备份cp /etc/pam.d/vsftpd /etc/pam.d/vsftpd.bak vim /etc/pam.d/vsftpd推荐写入的最小配置auth required pam_userdb.so db/etc/vsftpd/vusers account required pam_userdb.so db/etc/vsftpd/vusers这里有一个特别容易踩的坑db参数指向数据库文件路径时不能带 .db 后缀。也就是说文件是 /etc/vsftpd/vusers.db这里要写 /etc/vsftpd/vusers不带后缀。我带过的人里有好几个就是在这里多写了个 .db结果 PAM 一直报错找不到文件。PAM 配置里 auth 负责验证密码account 负责检查账号是否允许访问。两行都指向同一个数据库一个管你是谁一个管你能不能进来。3.5 验证数据库是否正确生成这一步很多人省略但我觉得值得做。用 db_dump 或者 strings 快速看一眼数据库里内容是否完整db_dump -p /etc/vsftpd/vusers.db | head -20能看到 user01、user02 这样的关键字基本就说明转换成功了。如果 db_dump 提示格式不对重新检查 db_load 参数。4. vsftpd.conf 核心配置让虚拟用户真正生效4.1 关键配置项逐一拆解改完 PAM 后真正的主角是 /etc/vsftpd/vsftpd.conf。大多数人配置虚拟用户失败问题基本都出在配置项的排列组合上。下面这份是我实测可用的完整模板直接粘贴后按你自己的规划调整路径即可anonymous_enableNO local_enableYES write_enableYES local_umask022 dirmessage_enableYES xferlog_enableYES connect_from_port_20YES xferlog_std_formatYES pam_service_namevsftpd guest_enableYES guest_usernamevftp virtual_use_local_privsYES user_config_dir/etc/vsftpd/vuser_conf chroot_local_userYES allow_writeable_chrootYES pasv_enableYES pasv_min_port30000 pasv_max_port30050逐个解释关键项guest_enableYES是总开关打开之后 vsftpd 才启用虚拟用户模式。guest_usernamevftp指定所有虚拟用户登录后映射到的系统用户必须和前面 useradd 创建的一致。virtual_use_local_privsYES是我特别想说的一项。默认情况下虚拟用户会被当作匿名用户同级别的权限来处理也就是各种写权限要用 anon_upload_enable、anon_mkdir_write_enable 这些参数去控制非常绕。设置成 YES 之后虚拟用户就获得本地用户级别的权限写权限只受 write_enable 和目录的实际属主权限影响配置和维护都清爽很多。chroot_local_userYES把所有本地用户和虚拟用户都锁定在自己的主目录里这样用户登录后只能在指定目录里活动看不到服务器其他路径这是多用户 FTP 场景下的安全底线。allow_writeable_chrootYES是为了一件事vsftpd 从 2.3.5 之后的版本如果用户主目录本身可写配合 chroot 模式会直接报 500 OOPS refusing to run with writable root inside chroot。加这一项可以绕过限制但生产环境更推荐的做法是主目录只读、写操作放到子目录里这样 chroot 仍然有效。我个人的习惯是测试阶段直接加这项正式上线前把目录结构调整好再移除它。user_config_dir指定每个虚拟用户独立配置文件的存放目录下面会细说。pasv_min_port和pasv_max_port是被动模式端口范围后面防火墙放行要用别漏。4.2 user_config_dir 机制一人一配置虚拟用户最实用的功能就是通过 user_config_dir 做差异化管理。vsftpd 会在指定的 /etc/vsftpd/vuser_conf 目录下查找用户名同名文件加载其中的配置项覆盖主配置里的相应设置。创建目录mkdir -p /etc/vsftpd/vuser_conf给 user01 单独设主目录和写权限vim /etc/vsftpd/vuser_conf/user01写入local_root/data/ftp/user01 write_enableYES local_umask022这样 user01 登录后看到的根目录是 /data/ftp/user01其他虚拟用户不受影响。目录一定要提前创建好并设置正确的属主mkdir -p /data/ftp/user01 chown vftp:vftp /data/ftp/user014.3 为什么 anonymous_enable 必须关虚拟用户模式下如果 anonymous_enable 还开着会同时开放匿名访问通道页面会暴露公共目录等于留了个后门。代码里把 anonymous_enableNO 写死不要手软。匿名功能这条线真的需要的时候再单独开和虚拟用户混在一起只会让排障变得痛苦。5. 网络放行与客户端实测SELinux、firewalld、MobaXterm5.1 SELinux 的正确放行方式CentOS Stream 10 默认 SELinux 处于 Enforcing 模式这也是很多人在本地测试一切正常、一到新装服务器就文件夹列不出来的根本原因。vsftpd 相关的 SELinux 布尔值比较多最省事的做法是直接放行 ftpd_full_accesssetsebool -P ftpd_full_access 1-P表示永久生效重启后依然保留。如果不想开放得那么彻底可以按需放开ftp_home_dir和ftpd_use_passive_mode但我建议先用 ftpd_full_access 把流程跑通之后再根据实际环境收紧。检查当前状态getsebool -a | grep ftpdSELinux 拦截时的典型现象是认证能通过ls 也能列目录但上传文件时直接 553 Permission denied查看/var/log/audit/audit.log里会有一堆 avc denied 记录。看到 avc 字样十有八九就是 SELinux 拦路了。5.2 firewalld 放行 FTP 与被动端口CentOS Stream 10 默认用的防火墙是 firewalld。先放行 FTP 服务本身firewall-cmd --permanent --add-serviceftp firewall-cmd --reload同时把 vsftpd.conf 里配置的被动端口范围一起放行否则客户端能连上但传输时会卡住firewall-cmd --permanent --add-port30000-30050/tcp firewall-cmd --reload这里解释一下为什么必须放行被动端口FTP 有主动和被动两种模式现在的办公网络和 NAT 环境下基本都是被动模式。被动模式下服务器会打开一个范围内的随机端口与客户端传数据如果防火墙不放行控制连接是通的数据通道却握手不了表现就是登录成功但目录加载不出来。5.3 客户端连接命令行、FileZilla 与 MobaXterm服务端全部配好后开始验证。最简单的测试方式是本机用 ftp 命令行连自己的 IPftp 服务器IP输入 user01 和密码能进入目录并执行 ls 就说明认证体系已经通了。我日常更喜欢用 lftp它的交互体验好得多dnf install -y lftp lftp user01服务器IP进入 lftp 后执行ls put /etc/hosts上传成功之后再用 get 拉回来确认读写都正常。Windows 环境下用 FileZilla 是最直观的主机填 IP用户名填 user01密码填虚拟用户密码端口默认 21连接类型选仅使用普通 FTP不要选 SFTP。顺带回应一个最近总有人问的问题MobaXterm 可以当 FTP 服务器吗严格回答是不可以。MobaXterm 本身是一个增强型的 SSH 客户端它内置的 SFTP/SCP 文件浏览功能确实可以让你像操作 FTP 一样上传下载文件但它服务端依赖的是 SSH 服务真正要让别人通过 21 端口用 FTP 协议来访问你的文件服务端仍然需要部署 vsftpd 这类 FTP 服务器程序。MobaXterm 更适合当作管理工具箱来连接我们刚搭建好的 FTP 服务。6. 常见问题排查实录与进阶玩法6.1 报错速查表解决九成以上虚拟用户问题虚拟用户配置翻车的情况虽然多但归根到底就是那么几个原因。我把踩过的坑整理成一份速查表建议你截图存一份现象大概率原因解决方案530 Login incorrectPAM 配置或数据库路径不对检查 pam_userdb.so 的 db 参数是否带 .db 后缀重建数据库500 OOPS: refusing to run with writable root inside chrootchroot 锁定目录可写在主配置加 allow_writeable_chrootYES 或调整目录权限553 Could not create file目录属主不对、SELinux 拦截确认目录 chown vftp:vftp检查 audit.log 里的 avc denied连上后目录列表为空SELinux 未放行 ftpd_full_accesssetsebool -P ftpd_full_access 1 后重试登录成功但传输卡死防火墙未放行被动端口放行 vsftpd.conf 里的 pasv_min_port / pasv_max_portPAM 错误 / could not read PAM config/etc/pam.d/vsftpd 被改坏用备份恢复再对照上面给出的配置重写服务启动失败vsftpd.conf 语法错误执行 vsftpd -olistenYES 查看输出定位具体参数db_load 命令找不到libdb-utils 未安装dnf install -y libdb-utils6.2 500 OOPS 最常见的两类展开说第一类是 chroot 根目录可写报错里一定有 refusing to run with writable root。按前面说的加 allow_writeable_chrootYES 能立刻解决但这个做法会削弱 chroot 的隔离效果因为可写的根目录给了用户更大操作空间。生产环境我建议把用户家目录设为 755只读然后再在下面建 upload 子目录并设为可写这样既满足上传需求又保证了隔离。第二类是 500 OOPS: could not bind listening socket多半是端口被占用用ss -lntp | grep 21查一下谁占了 21 端口一般就是已经有别的 FTP 进程在跑重启服务前先确认没冲突。6.3 进阶玩法一限制带宽与最大连接数虚拟用户方案跑通之后可以做一些精细化的资源限制防止某个用户把带宽占满。在主配置或用户独立配置文件中加max_clients50 max_per_ip5 local_max_rate1024000local_max_rate单位是字节/秒1024000 大约等于 1MB/s。按用户限制带宽时把它写到 /etc/vsftpd/vuser_conf/user01 这样的独立配置文件里就能实现user01 限速 1MBuser02 不限速的效果。6.4 进阶玩法二被动模式配合内网 NAT 映射服务器如果处在内网公网入口做了端口转发FTP 的被动模式还需要指定对外 IP否则客户端拿到的是内网 IP连不上数据端口。在 vsftpd.conf 里加pasv_address公网IP如果你的出口 IP 是动态的配不上固定值那就需要配合脚本定期更新这个参数或者干脆考虑用 SFTP 替代。说句实话遇到动态公网 IP 的场景继续用 FTP 僵持下去是很痛苦的一件事早点切 SFTP 更省心。6.5 进阶玩法三虚拟用户与目录权限模板最好再给新人一个目录权限的通用模板。多用户 FTP 环境里我通常这样设计目录结构/data/ftp # vftp 用户主目录权限 755只读 /data/ftp/user01 # user01 的家目录权限 755 /data/ftp/user01/upload # 上传目录权限 775属主 vftp /data/ftp/user01/download # 下载目录权限 755只读对应命令mkdir -p /data/ftp/user01/upload /data/ftp/user01/download chown -R vftp:vftp /data/ftp/user01 chmod 755 /data/ftp/user01 chmod 775 /data/ftp/user01/upload这样做的好处是用户能往自己的 upload 目录写东西但没办法改动根目录结构管理员发布文件时放到 download 目录就能让用户组只读下载。权限模型在 FTP 场景里越简单越稳别玩那些花里胡哨的 ACL 组合出了问题你很难跟同事解释清楚。把虚拟用户这套跑通之后再回头看本地账号方案确实觉得回不去了。账号密码完全独立权限能细化到一人一目录要关停某个人的访问直接删数据库记录就行不用去动系统用户。内网环境这样用完全没问题但服务器有公网入口的话得提醒你一句FTP 是明文协议密码和文件内容在网络上都是裸奔的建议尽快把被动端口范围、登录失败策略和 fail2ban 一起配上或者干脆切换到 SFTP。下一篇我打算写 vsftpd 的加固与日志审计把限速、登录次数限制和 xferlog 日志分析展开聊聊。