搞网络和运维的朋友,几乎都绕不开这三个缩写:FTP、TFTP、SFTP。这些年我见过太多人把SFTP当成“FTP加了一层加密”,也有人以为TFTP是“功能更少的FTP”,还有人傻乎乎地用FTP往公网服务器传文件,结果账号密码被扫描器拿走,服务器被人拖得干干净净。这篇文章想把工作里面绕不开的这三个文件传输协议讲透:它们各自凭什么活到今天、底层是怎么设计的、实际部署要注意什么、我在真实环境里踩过哪些坑。适合刚入门需要分清FTP/TFTP/SFTP的小白,也适合已经用着某个协议但说不清问题根因的工程师。
1. 三个都叫“传文件”,走的却是三条完全不同的路
先说结论:FTP、TFTP、SFTP这三者的名字里都有“F”和“T”,但它们并不是一个家族的三代升级款。它们诞生于不同年代、解决的是不同问题、使用完全不同的底层机制。混为一谈,是很多故障的起点。
1.1 先给三兄弟画个像:年代、端口和设计哲学
FTP(File Transfer Protocol)是真正意义上的老大哥,最早的草案可以追溯到1971年,比TCP/IP协议栈成型还早,最终定型在RFC 959。它面向的是“有人有密码、有目录、要交互式管理文件”的场景,所以功能非常完整:登录认证、列举目录、切换目录、断点续传、删除改名,样样都有。
TFTP(Trivial File Transfer Protocol)的“Trivial”说明了一切,它要的就是“极简”。RFC 783只规定了读文件、写文件,连目录列表都没有,没有认证,没有断点续传,所有报文都是固定结构。它存在的目的很简单:给那些还没装系统、没法跑复杂协议的设备做引导和数据搬运。
SFTP虽然名字里有“FTP”,但它实际上属于SSH协议家族,全称是SSH File Transfer Protocol。SSH在22号端口上完成握手和认证之后,SFTP只是SSH通道里运行的一个子协议。也就是说,它走的是加密信道,跟传统FTP的控制连接、数据连接那套机制几乎没有关系。
1.2 破除一个流传最广的误会:TFTP和SFTP都不是FTP的简单改版
很多人按字母顺序推测,觉得TFTP是FTP砍掉功能的精简版,SFTP是FTP补上加密的升级版。这个理解错得很干净,我直接拆开看:
- FTP的传输控制模式是“双连接”——控制连接发命令,数据连接传文件。它的认证、命令全部明文。
- TFTP直接抛弃了“控制连接”的概念,只用UDP报文在69号端口附近完成全部动作。它更接近一个“网络上的read/write指令”。
- SFTP既不建立在FTP的控制连接之上,也不使用数据连接,更不是在FTP报文外面包一层TLS。它是SSH安全的传输管道里跑二进制消息,所有文件操作都被封装成一条条SSH内部消息。
所以正确的图谱应该是:FTP是独立的文件协议,TFTP是独立的极简文件协议,SFTP是SSH体系内的文件服务。三者血缘上可以说是平行关系,不是父子推进关系。
2. FTP的双连接机制:控制通道的体面与数据通道的难堪
FTP最经典、也最让人头疼的设计,就是它把命令和数据拆到两条连接里。这条设计在当年很聪明,放到今天的防火墙和NAT环境里,又成了很多故障的来源。
2.1 控制连接和数据连接为什么要分开
FTP建立会话时,客户端首先连服务器的TCP 21端口,这条连接叫控制连接。你在终端里敲的USER、PASS、CWD、RETR、STOR都是作为普通的ASCII文本行,通过这条连接发给服务器,服务器的应答码(比如220、230、350)也是从这条连接回来。
真正要传文件内容时,系统再单独建立一条数据连接。设计者的考虑是:控制连接是短交互,数据连接是长传输。如果混在一起,文件传输过程中用户就没法发中止指令了;拆开之后,传着大文件还能通过控制连接发STAT看进度、发ABOR中断传输。这个思路在当时非常优雅。
但代价是:每传一次文件,就要动态建立一条新连接。这就引出了主动模式和被动模式两种数据连接建立方式,也成了FTP运维里绕不开的分水岭。
2.2 主动模式、被动模式与防火墙时代的妥协
主动模式(PORT模式)下,客户端在自己的控制连接里告诉服务器“我的数据端口是xxxx”,然后服务器主动从自己的TCP 20端口去连客户端的那个端口。FTP刚发明的年代,机器都是公有主机,客户端端口对服务器完全可见,主动模式毫无问题。
可今天的客户端大多在NAT后面,甚至本地防火墙默认拒绝外来主动连接。服务器根本连不进客户端的局域网IP。于是被动模式(PASV模式)成了主流:客户端发PASV命令,服务器返回一个自己开放的高端口,由客户端主动发起连接。
需要注意,被动模式下服务器告诉客户端的地址是可以被配置的。比如用vsftpd时,如果服务器在NAT后面,而你没有设置pasv_address,它就会把内网IP告诉客户端,结果客户端从公网根本无法连接这个内网IP。这里的完整排坑逻辑,我会在第六章展开。
2.3 明文认证与数据裸奔:为什么公网FTP几乎等于“裸奔”
FTP从设计上就没有加密这个概念。USER、PASS、每次传输的文件数据,全部以明文在网络上跑。只要有抓包工具,还原FTP密码就是丢包里面翻一下的事。更糟糕的是,很多扫描器专门盯着21端口做暴力破解和匿名登录探测。
所以我现在对公网服务器上的纯FTP服务基本是零容忍态度。真要对外提供文件服务,就走SFTP或者FTPS。FTPS本质还是FTP命令集,外面套了TLS加密,它保留了双连接和应答码,但兼容性和防火墙处理比SFTP麻烦不少;而SFTP则完全不同,后面我专门讲。
3. TFTP:UDP之上的极简文件搬运工
如果把FTP比作一个功能齐全的网盘客户端,那TFTP就是一张写了“把这段数据从A搬去B”的便条。它没有协议版本协商、没有认证,连“文件传输完成”都是靠数据包长度来暗示的。
3.1 五类报文和512字节块:TFTP的协议骨架
TFTP采用UDP传输,请求统一发往UDP 69端口。报文一共只有五类:
| 类型 | 数值 | 作用 |
|---|---|---|
| 读请求RRQ | 1 | 请求从服务器下载文件 |
| 写请求WRQ | 2 | 请求向服务器上传文件 |
| 数据DATA | 3 | 携带实际文件内容 |
| 确认ACK | 4 | 对数据包的确认 |
| 错误ERROR | 5 | 报告各种错误状态 |
传输逻辑简得让人舒适:客户端发RRQ或WRQ,然后文件内容被切成固定512字节一块,以DATA包发送,接收方回ACK,发送方收到ACK再发下一块。文件结束时,如果最后一块不足512字节,接收方就知道传完了。
这个512字节的块大小是协议默认值。现代实现可以通过RFC 2348里的blksize选项协商更大的块,比如1468字节,用来减少ACK次数,但兼容性不一定好,很多嵌入式设备的实现并不支持这个选项。
3.2 停等、块号回绕与超时重传:简单不等于没有心机
TFTP每发一个数据块,必须等ACK确认才继续,这种“停等协议”特别慢,但实现非常简单。整个协议连拥塞控制都没有,网络一旦丢包,就只能靠超时重传兜底。在跨网段、高延迟的链路上,TFTP慢到让人崩溃是非常正常的。
还有个容易被忽略的问题:DATA包的块号从1开始递增,最多只能到65535。65535块按默认512字节算,大概就是32MiB出头的文件。超过这个大小,块号就会回绕到0,接收端有可能把某个新数据块误判成旧块导致数据错乱。RFC 1350对这种情况专门做了处理,要求发送端利用回绕后的空长度块来帮助接收方区分,这也是为什么TFTP在传大固件时偶尔会在尾声出错的原因之一。当然,现代实现一般会尽量避开这个边界,但作为传输协议,它确实有先天限制。
3.3 TFTP的真正用武之地:PXE引导、固件备份和临时救急
TFTP最广为人知的场景是PXE网络引导。机器通过DHCP拿到IP和TFTP服务器地址后,用TFTP下载pxelinux.0、内核、initrd等引导文件。UEFI环境同样如此,先用TFTP拉取EFI格式的引导程序。整个过程没有认证、没有加密,但在封闭局域网里完全够用。
另一个高频率场景是网络设备升级固件。像交换机、路由器、防火墙这类设备,官方升级文档里几乎都会写“把固件放进TFTP服务器,然后从设备侧fetch”。原因很简单:设备跑在裁剪过的系统上,没能力跑复杂客户端,TFTP客户端几十行代码就能实现,是成本最低的选择。
临时救急也会有用武之地。比如两台电脑在内网互相拷贝一个不大不小的文件,一时没有移动硬盘,开一个TFTP服务端,几十秒就能完成。前提是文件别太大、丢包别太多,否则会等得人怀疑人生。
4. SFTP:借SSH的光、吃SSH的饭的加密文件协议
我对SFTP最想强调的一点是:它跟FTP在协议层面几乎没有任何共同语言。你要是拿看FTP的思路去排查SFTP,大概率会把问题方向带偏。
4.1 SFTP为何不是“FTP加了个TLS”
SFTP全称是SSH File Transfer Protocol。它运行在SSH之上,默认端口就是SSH的22端口。客户端建立SSH连接后,隧道内部先完成密钥交换、主机密钥验证、用户认证,一切都加密了业务才能开始。之后客户端发送SSH_FXP_INIT等二进制消息,协商子系统的版本号,再通过SSH_FXP_OPEN、SSH_FXP_READ、SSH_FXP_WRITE这些消息执行文件操作。
这个过程和FTP那套“先开21控制连接、再开数据连接传文件”的结构完全不同。SFTP的所有操作都在同一条加密的SSH通道里完成,不存在“主动被动模式”,也不需要额外开放一堆数据端口。对防火墙来说,只需要放行22端口,策略极其干净。
4.2 SFTP与FTPS:加密方式完全不同
FTPS是File Transfer Protocol over SSL/TLS,本质上是传统FTP命令集外面套了一层加密会话。它分成显式(explicit,客户端先发AUTH TLS)和隐式(implicit,默认990端口直接开始TLS握手)两种,接通后仍然在跑FTP的命令、应答码和主动被动模式。
这就带来一个典型问题:FTPS虽然传输是加密的,但协议栈还是老FTP那套,控制连接上依然要进行PORT/PASV协商,后端防火墙要维护很多动态数据端口。SFTP不需要这些,一条SSH通道全部搞定。
安全上,FTPS的证书、加密强度取决于TLS配置,而SFTP的安全性继承SSH的完整体系:密钥交换、主机密钥指纹校验、公钥认证。实际对接第三方系统时,SFTP几乎是默认选项,很多银行和大型企业只允许SFTP。
4.3 除了get/put,SFTP还能当远程文件系统用
SFTP的另一个优势是消息非常丰富。除了读写文件,它还支持列目录、创建目录、改名、删除、修改权限、创建符号链接、获取文件状态,甚至文件锁。OpenSSH自带的sftp子系统基本把所有日常运维需要的文件操作都覆盖了。
实际使用中,我把SFTP当作一个“安全的远程文件管理协议”来对待。它比NFS省心,不需要内核模块和复杂权限映射;比FTP安全,不存在明文裸奔问题;比WebDAV通用,几乎每一台Linux服务器默认都开了SSH和sftp子系统。配合密钥登录,还能做到免密批量同步、基于SSH的自动运维脚本。这些能力是传统FTP很难媲美的。
5. 选型决策和服务端部署:用一张表加三段配置说话
现在回到最实际的问题:我手上有个场景,到底该用FTP、TFTP还是SFTP?下面是我自己平时做决策的完整思路。
5.1 什么场景该用哪个协议:一张决策表
| 场景 | 推荐协议 | 理由 |
|---|---|---|
| 公网或跨机构文件交换,需要加密和审计 | SFTP | 加密、22端口单一、密钥认证、三方系统普遍支持 |
| 封闭内网内部文件下载,客户端设备老旧 | FTP | 兼容性好,部署简单,没有安全暴露面即可接受 |
| 局域网网络设备固件升级、无盘引导 | TFTP | 设备固件内置客户端,无系统环境也能运行 |
| 需要旧系统对接,且明确要求FTP命令兼容 | FTPS | 保留FTP命令集,同时加TLS加密 |
| 快速在局域网临时传小文件 | TFTP或FTP | 都能用,文件小选TFTP更快落地,文件稍大选FTP |
我个人的原则是:凡是客户端能装或系统自带现代工具的,优先SFTP;凡是设备固件或引导场景,优先TFTP;只有在“双方都有明确FTP兼容要求”时才用传统FTP或FTPS。
5.2 服务端快速部署:vsftpd、tftp和OpenSSH的最小可用配置
以Linux为例,FTP服务我常用vsftpd,一份最小可用的被动模式配置长这样:
anonymous_enable=NO local_enable=YES write_enable=YES local_umask=022 dirmessage_enable=YES xferlog_enable=YES pasv_enable=YES pasv_min_port=10000 pasv_max_port=10100 pasv_address=192.168.1.10这里的pasv_min_port和pasv_max_port定义了被动模式下服务器开放的高端口范围,防火墙要放行;pasv_address要填客户端实际可见的地址,NAT场景里格外重要。
TFTP服务在Linux上一般装tftpd-hpa,以systemd管理时核心配置在/etc/default/tftpd-hpa:
TFTP_USERNAME="tftp" TFTP_DIRECTORY="/var/lib/tftpboot" TFTP_ADDRESS=":69" TFTP_OPTIONS="--secure"把要提供的固件或引导文件放进/var/lib/tftpboot,确认权限可读,就能启动服务。如果走xinetd,也要在服务配置里指定-server_args为-s /var/lib/tftpboot,这个-s参数表示chroot到指定目录,是安全选项,建议保留。
SFTP服务器基本不用单独装,OpenSSH默认就带sftp子系统。关键配置在/etc/ssh/sshd_config里:
Subsystem sftp internal-sftp PasswordAuthentication no PubkeyAuthentication yes如果只想给特定用户限制SFTP并锁在自家目录,可以用Match段:
Match Group sftponly ChrootDirectory /srv/sftp/%u ForceCommand internal-sftp X11Forwarding no AllowTcpForwarding no这段有个非常容易踩的坑:ChrootDirectory指向的目录必须由root拥有,而且所属组不能带写权限,否则SSH会直接断开连接。我见过太多人在这里配置完后“Connection closed”半天找不到原因。
5.3 防火墙与NAT放行规则:每个端口后面的设计逻辑
| 协议/模式 | 需要放行的端口 | 说明 |
|---|---|---|
| FTP主动模式 | 21、20 | 需要服务器主动连客户端,NAT内侧基本不可用 |
| FTP被动模式 | 21 + 10000-10100 | 数据端口范围要跟vsftpd配置一致 |
| TFTP | UDP 69 + 动态高端口 | 服务器收到请求后可能从随机端口回包,NAT穿透较差 |
| SFTP/SSH | 22 | 单一端口,规则最简洁 |
TFTP的防火墙规则经常被人误解成“放UDP 69就行”。实际上服务器收到RRQ以后,完全可能从一个临时源端口发送数据内容,客户端要回去应答的是那个临时端口。在简单环境里放UDP 69能工作,但更稳妥的做法是放行源端口69和对应的临时高端口范围。这也是TFTP在跨NAT场景下很难用的核心原因。
5.4 我实际用下来的性能和建议
FTP传大文件,限制速度用vsftpd的local_max_rate,单位是字节每秒,配合带宽规划才不至于把业务链路打满。TFTP传大文件,如果链路丢包率高,默认512字节块的效率惨不忍睹,能协商blksize就尽量协商。SFTP传大文件,SSH内部加密解密有CPU开销,建议换用更快的加密算法,比如aes128-gcm,同时确认网卡和CPU没有成为瓶颈。
6. 我踩过的坑:被动模式、TFTP卡末尾和SFTP目录权限
写文章不交代实际排坑过程,等于只给菜谱不教火候。下面三个坑都是我在真实环境里遇到、且带着别人复现过的,每一个都值得你记住。
6.1 FTP被动模式下公网端口映射错乱的排错过程
有段时间内网有个FTP服务器,NAT映射到公网后,内网客户端能下载,外网客户端一传文件就卡住。我让同事在客户端抓包,发现连接21端口一切正常,发了PASV之后,服务器响应里给了一个内网地址,客户端拿到这个内网地址当然连不上。
这就是典型的pasv_address没配置。vsftpd默认按本机网卡IP返回给客户端,NAT后面的服务器返回的是内网IP,客户端远在公网,自然无法建立数据连接。修复分两步:第一,在vsftpd.conf里把pasv_address设为公网IP;第二,在防火墙上放行对应的被动端口段,比如10000-10100。改完重启服务再抓包,PASV响应里的地址变成公网IP,数据连接秒开。
顺带说一句,排查这类问题最快的工具不是猜,而是抓包:客户端卡住时,在客户端和服务端同时看包的方向和目标端口,一眼就能判断数据连接到底断在哪跳。
6.2 TFTP大文件传输卡在最后一块的真相
某个设备升级固件,TFTP传到95%到99%之间时就停住,等几分钟才超时失败。第一反应以为网络丢包,后来在链路上对比测试,发现问题跟UDP超时和块号边界都有关系。
TFTP是停等协议,丢一个包就要等重传。默认超时时间是客户端侧设定,很多实现是5秒左右,跨网段一旦丢包率高,重传等待会成倍拖慢。更糟的是,文件接近32MiB边界时,块号回绕处理逻辑在一些老设备固件实现里很粗糙,哪怕多了几个字节的偏差,接收端都会停下来。最终对症方案是:能协商blksize的协商更大块,不能协商的把文件控制在32MiB内,同时把链路丢包先排查干净。这个坑提醒我一个道理:简单协议并不等于鲁棒协议,越简单的协议越依赖底层网络质量。
6.3 SFTP的ChrootDirectory卡权限:目录归属问题排查
给某部门开SFTP专用账号,配置文件写好后,客户端一连接就提示“Connection closed”,服务端日志里没有明确报错。排查过程首先是关掉日志的冗长级别看sshd的输出,通常能看到“fatal: bad ownership or modes for chroot directory”。
原因就是ChrootDirectory指向的目录所有者不是root。SSH为了实现chroot,必须保证目标目录不被非root用户写控制,所以目录归属必须是root,权限一般建议755。我把/home/sftp目录改回root所有,再去掉组写权限,连接立即正常。
另一个同类问题是目录权限太宽松,比如/home/sftp/user这个用户家目录被设成777,sshd同样会拒绝。经验就一句话:把SSH的SFTP锁目录当成“监狱”来设计,监狱的门必须由狱长root牢牢控制,里面的活动空间再给用户也不迟。
这三个坑的共性都是“协议设计逻辑”决定的:FTP的PASV把寻址责任交给了服务器,TFTP的停等机制把效率责任交给了网络,SFTP的chroot把权限责任交给了目录归属。搞懂设计逻辑,很多表面上的随机故障,其实都有必然答案。