资讯详情

FTP服务器从vsftpd搭建到Python脚本化上传下载全攻略

📅 2026/10/17 4:44:59 | 华诺云谱 👁 阅读
FTP服务器从vsftpd搭建到Python脚本化上传下载全攻略
简介这是一份面向Java开发者的FTP/SFTP文件传输工具包解决从服务器上传、下载文件这一常见需求适合需要快速集成文件传输能力的初学者、运维人员及项目开发人员参考复用。包内共2个文件包含一个Java源码文件与一个JSch依赖JAR包压缩包整体仅106KB体积小巧、结构清晰可直接将源码和依赖引入工程后修改服务器地址、账号密码即用。源码中封装了与服务器建立连接、登录验证、上传与下载文件、切换工作目录、设置二进制传输模式以及断开连接等关键操作并配有完整的调用示例能帮助读者理解基于SSH的SFTP传输实现方式同时也可对照FTP基础知识掌握两类协议在工作模式、异常处理上的差异。已有251人学习下载适合作为Java网络编程入门的配套练习也可作为轻量级文件传输模块的种子代码。1. FTP从服务器上传下载文件老协议为什么还值得认真学一遍你手里握着一台服务器要把打包好的静态资源传上去或者把服务器上的日志拉回本地分析。很多人第一反应是装个图形化工具但真正到了脚本化、批量同步、丢给定时任务自动跑的场景FTP协议反而是最不容易出岔子的那条路。它虽然诞生得早但传输模型简单、调试可观测性高搞懂主动/被动模式、端口段、chroot边界之后稳定程度远超直觉。这篇笔记从服务端搭建开始把上传、下载、目录批量同步、防火墙放行、编码和断点续传整个链路拆开讲。不需要图形界面一条命令、一段 Python 脚本就能让文件在两台机器之间稳定流动。适合正在做课程设计、需要把本地资源部署到服务器的人也适合定期从服务器拉取推送数据的开发者对 FTP 只知其名不知其配置细节的人更要看。2. 搭建可用的FTP服务端从安装到联通的完整链路2.1 服务端选型为什么首选 vsftpd提到 FTP 服务端Linux 生态里最常见的开源实现是 vsftpdVery Secure FTP Daemon它配置精简、性能好、安全默认值高几乎每个主流发行版的软件源里都有安装包小、依赖少、配置文件只有一个坏了也好排查。另一个常见选择是 Pure-FTPd配置风格偏“开箱即用”不少虚拟主机面板会集成它。如果你只是想在自己的服务器上搭一个稳定的文件交换通道我一般直接选 vsftpd。有人会说 FTP 过时了用 SFTP 不是更省事SFTP 走的是 SSH 的 22 端口如果你本身开通了 SSH那确实 scp 一条命令就走人。但 FTP 的生命力在于不少老旧的嵌入式设备、NAS、网络打印机只支持 FTP 协议另外如果你需要的是一批只能读写特定目录、不能登录 shell 的账号FTP 配合 chroot 就是一个干净又容易审计的隔离方案。所以下面整个链路以 vsftpd 为主线来演示。2.2 安装与启动三步确认服务在跑# 在Linux服务器上安装vsftpd按发行版选一条执行 # Debian/Ubuntu系 sudo apt-get update sudo apt-get install -y vsftpd # RHEL类发行版 # sudo yum install -y vsftpd # 启动服务并设置开机自启 sudo systemctl start vsftpd sudo systemctl enable vsftpd # 检查端口监听状态确认21号端口在LISTEN sudo ss -tlnp | grep :21这段操作的价值在于快速确认服务真的起来了。systemctl enable解决服务器重启后 FTP 服务不自动拉起的问题ss -tlnp是排查第一步如果端口没出现多半是安装失败或配置里有语法错误systemctl status vsftpd能看到具体报错。注意默认端口就是 21其他 Web 服务不要占掉它否则会出现“服务明明装了但客户端永远连不上”的怪象。2.3 核心配置文件逐项拆解vsftpd 的配置集中在/etc/vsftpd.conf部分发行版还会读取/etc/vsftpd/下的子配置。下面这份是我在模拟项目X里常用的基准配置每一行都直接影响后续上传下载能否成功# 允许本地系统账号登录FTP local_enableYES # 允许写操作上传、删除、重命名 write_enableYES # 本地用户登录后限制在自己的home目录内防止越权看系统文件 chroot_local_userYES # 允许chroot后的目录可写老版本默认禁止向home根目录写入 allow_writeable_chrootYES # 启用被动模式并固定数据端口范围 pasv_enableYES pasv_min_port30000 pasv_max_port31000 # 关闭匿名访问避免服务裸奔 anonymous_enableNO很多人只记得开local_enableYES却忘了write_enableYES结果客户端能登录但一旦上传就收到550 Permission denied。chroot_local_userYES会把账号锁在 home 目录里是个非常重要的安全边界allow_writeable_chrootYES则是为了兼容新版 vsftpd 默认禁止向 chroot 根目录写文件的策略。pasv_min_port和pasv_max_port是第 4 章排查卡死的关键这 1000 个端口的射程范围要记牢。2.4 创建专用FTP账号与目录# 创建专用账号不分配登录shell不允许通过SSH进入服务器 sudo useradd -m -s /usr/sbin/nologin ftpuser # 设置登录密码 sudo passwd ftpuser # 建立数据目录并级联授权 sudo mkdir -p /home/ftpuser/data sudo chown ftpuser:ftpuser /home/ftpuser/data-s /usr/sbin/nologin是这个账号安全性的核心它让 FTP 用户无法通过 SSH 登录服务器只能通过 FTP 协议访问文件。目录属主一定要改成ftpuser否则客户端登录后能看目录列表一旦尝试写入就会撞上权限错误。如果你希望把多个业务目录挂进来比直接改 home 路径更干净的做法是mount --bind /外部目录 /home/ftpuser/data/业务子目录这样既不需要给账号开系统级访问权限又能把数据目录收拢到 home 之下。2.5 第一次连通性测试# 在服务器本机先自测排除网络因素 ftp localhost # 输入用户名 ftpuser 和密码后依次执行 # pwd - 显示当前FTP目录 # ls - 查看文件列表 # quit - 退出 # 看到 230 Login successful 即通过本地自测能过说明服务端配置基本没有硬伤远程连不上就往防火墙、被动模式、安全组方向排查。这一步把“服务端问题”和“网络问题”劈成两半后续排错会快很多。3. 上传与下载文件三种脚本化姿势3.1 Python ftplib标准库里的免费午餐Python 自带ftplib不需要 pip 装任何依赖写一个上传下载工具是几分钟的事。下面这段代码是我常用的骨架加了超时控制和二进制模式# ftp_transfer.py # 用法: python3 ftp_transfer.py upload local.txt remote.txt import sys from ftplib import FTP def connect(host, user, pwd, port21): ftp FTP() ftp.connect(host, port, timeout10) # 10秒超时避免死等 ftp.login(user, pwd) return ftp def upload(ftp, local_path, remote_path): with open(local_path, rb) as f: ftp.storbinary(fSTOR {remote_path}, f) print(fuploaded: {local_path} - {remote_path}) def download(ftp, remote_path, local_path): with open(local_path, wb) as f: ftp.retrbinary(fRETR {remote_path}, f.write) print(fdownloaded: {remote_path} - {local_path}) if __name__ __main__: ftp connect(你的服务器IP, ftpuser, 你的密码) if sys.argv[1] upload: upload(ftp, sys.argv[2], sys.argv[3]) elif sys.argv[1] download: download(ftp, sys.argv[2], sys.argv[3]) ftp.quit()storbinary负责把本地二进制流写入服务器对应 FTP 的STOR指令retrbinary把服务器文件取回并回调写入对应RETR指令。文件必须用二进制模式打开否则 Windows 下换行符会被悄悄改写图片、压缩包会直接损坏。timeout10是我刻意加的默认阻塞模式在服务端不稳定时会让脚本一直挂住设置超时能让失败早暴露、早重试。3.2 目录批量同步遍历、建目录、比大小单文件传输只是开胃菜实际工作里最常用的是目录级同步。下面这个脚本递归遍历本地目录在服务器上自动创建缺失的目录并只上传与服务器大小不一致的文件# sync_upload.py import os from ftplib import FTP def ensure_remote_dir(ftp, remote_dir): 在FTP服务器上递归创建目录已存在则跳过 parts remote_dir.split(/) path for p in parts: if not p: continue path / p try: ftp.mkd(path) except Exception: pass def should_upload(ftp, remote_path, local_size): try: remote_size ftp.size(remote_path) return remote_size ! local_size except Exception: return True def sync_upload(ftp, local_root, remote_root): for root, dirs, files in os.walk(local_root): rel os.path.relpath(root, local_root) remote_dir remote_root if rel . else f{remote_root}/{rel} ensure_remote_dir(ftp, remote_dir) for f in files: local_path os.path.join(root, f) remote_path f{remote_dir}/{f} size os.path.getsize(local_path) if should_upload(ftp, remote_path, size): with open(local_path, rb) as fp: ftp.storbinary(fSTOR {remote_path}, fp) print(upload:, remote_path) if __name__ __main__: ftp FTP(你的服务器IP) ftp.login(ftpuser, 你的密码) sync_upload(ftp, ./dist, /data) ftp.quit()这里的筛选逻辑是先调ftp.size()拿远端文件大小与本地不一致就上传。注意这只是初筛更严格的做法是比 MD5但 FTP 协议本身没有标准的 MD5 指令很多服务端会扩展支持MD5指令对部署场景来说大小比对已经能过滤掉绝大多数重复传输。os.walk返回的dirs列表可以按需剔除node_modules、.git这类大目录避免把上百兆依赖包推上服务器。3.3 老派 ftp 命令行与 curl不同场景的取舍# 用heredoc把命令一次喂给ftp适合快速手工批量操作 ftp -n 你的服务器IP EOF user ftpuser 你的密码 cd /data mput *.tar.gz prompt off bye EOF-n表示不自动登录后面的user命令手动完成认证mput支持通配符批量上传mget批量下载prompt off关闭逐文件确认否则文件一多会反复问 Yes/No。如果你只想快速推一个文件且本机装了 curl用curl -T local.tar.gz ftp://服务器IP/data/也行curl 会自动处理认证参数。但注意 curl 的 FTP 上传默认用的也是被动模式服务器同样要放行数据端口段。命令行派适合临时应急脚本里还是建议用 Python 加环境变量读密码别把凭据硬编码在 shell 脚本里。3.4 下载整个远程目录的小补充# download_dir.py import os from ftplib import FTP def download_dir(ftp, remote_dir, local_dir): os.makedirs(local_dir, exist_okTrue) for name in ftp.nlst(remote_dir): # 列出远端目录所有条目 local_path os.path.join(local_dir, os.path.basename(name)) with open(local_path, wb) as f: ftp.retrbinary(fRETR {name}, f.write) print(fdownloaded: {remote_dir} - {local_dir}) ftp FTP(你的服务器IP) ftp.login(ftpuser, 你的密码) download_dir(ftp, /data/logs, ./local_logs) ftp.quit()这个版本的局限在于nlst不递归子目录碰到目录里还有目录时需要手动判断ftp.nlst返回的是文件还是目录再决定是递归还是下载。更省事的方案见第 6 章的断点续传与校验组合。4. 被动模式、防火墙与chroot打通远程连接的三个关键4.1 主动模式与被动模式到底谁连谁FTP 和 HTTP 有一个本质区别它不只用 21 端口传命令还会额外开端口传数据。主动模式下客户端开一个随机端口并告诉服务器“你来连我这个端口”服务器用 20 端口主动连回去。这个模型在内网环境下大概率被防火墙拦死因为服务器对外主动发起连接通常不受允许。被动模式下服务器开放一个高端口区间等待客户端来连对客户端更友好所以现代 FTP 客户端和 curl 默认都在用被动模式。选型结论很简单服务端能开被动模式就开被动模式并固定端口段这是解决“连接通了但数据传不动”的最优路径。主动模式只有在极端网络策略下才会被拉出来应急日常开发别去折腾它。4.2 放行21端口与数据端口段# 先在/etc/vsftpd.conf中确认或添加 # pasv_enableYES # pasv_min_port30000 # pasv_max_port31000 # 重启服务使配置生效 sudo systemctl restart vsftpd # 以常见的firewalld举例放行端口 sudo firewall-cmd --permanent --add-port21/tcp sudo firewall-cmd --permanent --add-port30000-31000/tcp sudo firewall-cmd --reload这里最容易犯的错误是只放行 21 端口。很多人的现象是账号密码输完能过但一敲ls界面就像“卡死”一样长时间无响应十有八九是 30000-31000 这一段没放行。固定端口段的好处是给防火墙规则一个确定范围如果你在云服务器上安全组规则要同步放行这一整段 TCP 端口。4.3 chroot边界与allow_writeable_chrootchroot_local_userYES的意义在于FTP 账号登录后它看到的根目录就是自己的 home 目录往上无法访问/etc/passwd、/var/www这类系统路径从根上杜绝越权读取。启用 chroot 后老版本 vsftpd 出于安全考虑会禁止用户向 home 根目录直接写文件如果你创建的账号 home 目录本身可写且你尝试往根目录放文件服务端会回500 OOPS。显式加入allow_writeable_chrootYES是解决这个历史坑的标准动作。需要注意chroot 后mount --bind进来的外部目录依然可见可用但如果你绑定的目录属主不是该账号写入同样会权限失败。这个边界在设计目录归属时就要想清楚。4.4 NAT场景下的pasv_address# 当服务器在NAT网关后面客户端拿到的PASV地址会是内网IP导致数据连接失败 # 在vsftpd.conf里显式指定公网IP或域名 pasv_address你的公网IP或域名这个配置我在云服务器上实践时踩过好几次客户端能登录、能列出部分目录但传输大文件时随机中断。原因是服务器响应 PASV 指令时返回的是内网 IP客户端按照内网 IP 去连自然连不上。指定pasv_address后PASV 响应里的地址才会变成客户端能触达的公网地址。5. FTP踩坑实录五个高频问题的现象、原因与解决5.1 登录成功后ls卡死界面长时间无响应现象21 端口能通账号密码认证也通过了但敲入ls后客户端界面长时间停住最终超时断开。原因被动模式下服务器的数据端口段没有放行客户端去连30000-31000被防火墙或安全组拦截。另一个少见原因是 PASV 返回的地址是内网 IP客户端无法触达。解决检查vsftpd.conf里pasv_min_port与pasv_max_port是否正确用firewall-cmd放行对应端口段云服务器安全组也同步放行。如果服务器在 NAT 后面按 4.4 节设置pasv_address。5.2 530 Login incorrect但密码明明没错现象服务器上ftp localhost能登录远程客户端报530 Login incorrect。原因vsftpd 默认走 PAM 认证某些系统上nologin类型的账号会被 PAM 拒绝或者配置文件里开启了userlist_enableYES而账号恰好被写进了/etc/vsftpd.user_list拒绝名单。解决先用grep -v ^# /etc/vsftpd.conf | grep userlist看看是否开启了用户名单限制再查看/var/log/secure或/var/log/auth.log搜pam关键字定位 PAM 拒绝的具体原因。多数时候把账号从拒绝名单里拿掉即可。5.3 上传报553或550 Permission denied现象建目录正常put上传文件失败报553 Could not create file或550 Permission denied。原因两层可能一是write_enableNO根本没开写权限二是目标目录属主不是当前 FTP 用户账号没有写权限。如果系统启用了 SELinux还要考虑 FTP 写策略被拦。解决确认write_enableYESchown ftpuser:ftpuser把目录属主改过来SELinux 环境下执行setsebool -P allow_ftpd_full_access on放行写入。改完任何配置记得systemctl restart vsftpd。5.4 中文文件名全部变成乱码现象Windows 下用客户端上传“项目报告_v2.0.docx”服务器上看到绋稿一类乱码下载中文文件名反过来一样乱。原因本地 Windows 编码是 GBK/GB2312服务器和现代 FTP 客户端默认用 UTF-8。FTP 协议本身不规定文件名字符集全靠两端自行约定。解决客户端强制指定 UTF-8 编码命令行下用lftp并设置set ftp:charsetUTF-8用ftplib时对拿到的文件名按 UTF-8 解码如果服务端是 GBK 环境就按 GBK 解码。规范建议是服务端、客户端全链路统一成 UTF-8从源头上消灭乱码。5.5 传了一半的文件残留大小对不上现象大文件传输中途网络抖动连接断开服务器上留下一个不完整文件重跑又可能覆盖成更短的残次品。原因FTP 的STOR是覆盖式写入中途断开不会自动回滚已经写入的字节会保留在服务器上。解决上传大文件时先传.tmp后缀传完再rename成正式文件名脚本侧配合断点续传见第 6 章避免每次断线都从头开始。这套“临时文件 重命名”的套路能显著降低脏数据被下游误用的风险。6. 进阶技巧断点续传、定时同步与完整性校验6.1 用REST参数实现断点续传from ftplib import FTP import os ftp FTP(你的服务器IP) ftp.login(ftpuser, 你的密码) remote /data/big.tar.gz local big.tar.gz offset os.path.getsize(local) if os.path.exists(local) else 0 ftp.voidcmd(TYPE I) ftp.sendcmd(fREST {offset}) with open(local, ab) as f: ftp.retrbinary(fRETR {remote}, f.write)这段代码先看本地已有多少字节用REST告诉服务器偏移量服务器只发送剩余部分本地用ab追加模式写入。注意断点续传依赖服务器端文件没有被改动过否则拼接出来的文件错位但大小一致隐蔽性很强。所以我续传后一定会再做一次校验。6.2 用crontab做定时同步# 每天凌晨2点执行一次同步脚本日志落到固定文件 0 2 * * * /usr/bin/python3 /opt/sync_upload.py /var/log/ftp_sync.log 21日志一定要重定向到文件否则定时任务环境下任何报错都会被系统邮件吞掉排错时什么都看不到。6.3 MD5二次确认import hashlib def md5(path): h hashlib.md5() with open(path, rb) as f: for chunk in iter(lambda: f.read(65536), b): h.update(chunk) return h.hexdigest() # 若服务端支持MD5指令可拿返回值与本地比对 # server_md5 ftp.sendcmd(MD5 /data/big.tar.gz)流式读取分块计算 MD5 能避免一次性把大文件载入内存。如果服务器不支持MD5指令就下载完自行比对本地已算好的值。从那以后我每次搭 FTP 服务都会强制走一遍“被动端口放行、chroot 目录属主、全链路 UTF-8”这三件事给某公司做文件交换时也坚持“先传临时文件、校验后 rename”的规范。脏数据造成的返工远比多写几行脚本更痛。希望帮到你。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑