说个我自己遇到的场景。之前有个团队,内部文件分发一直用老式FTP,隔三差五出现传一半断掉、重传还得从头开始的问题,后来整体换到SFTP,运维才消停下来。什么叫SFTP?它不是一个独立安装的文件服务,而是基于SSH协议的文件传输子系统,默认走22端口,链路全程加密,也不像传统FTP那样需要额外放行一堆随机数据端口。这篇东西写给谁看:Ubuntu服务器管理员、偶尔在Linux机器间拷文件的开发、还有想搞明白文件传输原理的同学,都能照着下面步骤自己跑一遍。内容覆盖服务端搭建、权限隔离、客户端实操、断线续传和故障排查,全是在Linux环境里摸出来的经验。
1. 为什么是SFTP:Ubuntu下文件传输方案的真实取舍
1.1 它和FTP、SCP的差别,以及为什么省心
很多刚接触Linux的人会把SFTP和FTP搞混,觉得差一个字母而已。实际差别巨大。传统FTP使用21端口做控制连接,数据连接还要根据主动/被动模式另开端口,在NAT后面、防火墙策略严格的环境里非常难受。更要命的是FTP的账号口令和数据内容默认都是明文传输,网络里有人抓包,口令直接就没了。而SFTP全称是SSH File Transfer Protocol,走的是SSH协议内部的子系统,客户端和服务端先建立一条加密的SSH通道,然后在这个通道里执行文件操作。
SFTP比SCP也顺手得多。SCP其实只是一个基于SSH的远程复制命令,能传文件但做不了交互式文件管理,你想列远端目录、改名、删除,得再开一个SSH会话操作。SFTP像一个迷你的远程文件管理器,ls、cd、mkdir、rename、rm、chmod全支持,一次会话里全部搞定。所以说到底,SFTP是“能管理文件的SSH”,而不仅仅是“能传文件的SCP”。
| 对比项 | FTP | SCP | SFTP |
|---|---|---|---|
| 默认端口 | 21+随机数据端口 | 22 | 22 |
| 传输加密 | 默认明文,FTPS才加密 | 加密 | 加密 |
| 文件管理操作 | 支持 | 仅复制 | 完整支持 |
| 防火墙友好度 | 差,需放行动态端口 | 好 | 好 |
| Ubuntu默认是否具备 | 需额外安装FTP服务器 | OpenSSH自带 | OpenSSH自带 |
这是我在实际项目里最看重的一点:改了SFTP方案之后,防火墙只需放行22端口,不用再处理动态数据端口的事。曾经有台机器只开放了SSH端口,文件传输需求来了,我直接启用SFTP就解决,不用申请硬件防火墙策略变更,省了很多沟通成本。
1.2 Ubuntu下SFTP不用单独装服务端
Ubuntu系统只要装了openssh-server,SFTP的能力就默认跟着来了,不用额外来一个sftp-server包。很多人不知道这一点,花时间去查找“如何在Ubuntu安装sftp服务端”,其实打开/etc/ssh/sshd_config看一眼就知道,里面有一行:
Subsystem sftp /usr/lib/openssh/sftp-server这行就是SFTP子系统的入口定义。它告诉sshd:当客户端请求sftp子系统时,调用这个外部程序来处理。所以你的Ubuntu一旦能SSH登录,就已经具备SFTP服务能力。通过SSH的子系统机制来提供文件传输,这是OpenSSH设计上很优雅的地方,文件传输功能不是另行启动的独立服务,而是复用了SSH的安全通道。
搞清楚这个基本设定之后,后面讨论配置就顺了。下面进入实战。
2. 服务端从零配置:写进sshd_config的几个关键行
2.1 安装openssh-server并验证默认SFTP可用
如果机器上还没有装SSH服务,先执行安装:
sudo apt update sudo apt install -y openssh-server安装完成后,确认服务在运行:
systemctl status ssh如果服务不是active状态,先启动并设置开机自启:
sudo systemctl start ssh sudo systemctl enable ssh装完后建议立刻在本机验证一下SFTP基础功能:
sftp localhost能进入sftp>交互提示符就说明默认配置可用。先跑通这一步很重要,后续改配置如果出了问题,至少知道初始环境是好的,排错范围一下就缩小了。
2.2 sshd_config里和SFTP强相关的配置项
打开/etc/ssh/sshd_config,重点关注这些项:
- Subsystem sftp /usr/lib/openssh/sftp-server:SFTP子系统的外部实现路径。
- AllowUsers / AllowGroups:限制谁可以使用SSH相关服务。
- PasswordAuthentication yes/no:是否允许密码认证。
- PermitRootLogin prohibit-password:是否允许root直接登录,生产环境一般关掉。
- ChrootDirectory:配合ForceCommand,把用户限制在某个目录内。
- ForceCommand internal-sftp:强制用户只能执行SFTP子系统命令,不能获得shell。
这里有个容易被忽略的技术点:默认配置用的是外部程序/usr/lib/openssh/sftp-server,而做Chroot目录隔离时,官方推荐把Subsystem改成internal-sftp,让sshd内部实现直接处理SFTP协议。原因是外部sftp-server需要依赖用户目录下的二进制环境,一旦Chroot进去,用户可能连必要的库都找不到,而internal-sftp运行在sshd进程内部,不受Chroot环境依赖影响。
配置文件里改Subsystem那行:
Subsystem sftp internal-sftp虽然后续用Match Group的方案里也可以不显式改这行,但内嵌式处理在隔离场景下确实更稳定,我一般直接改掉。
2.3 改动配置前先备份、校验语法再重启
无论改什么,动手之前先备份:
sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak.$(date +%Y%m%d)改完任意参数,都要先用sshd命令行检查语法:
sudo sshd -t如果输出为空,说明配置语法通过。此时不要急着关掉当前连接,先另开一个终端测试新配置是否可用,确认无误后再重启服务:
sudo systemctl restart ssh为什么这么谨慎?因为sshd_config一旦写错,可能导致所有SSH登录全部失败,而你又关掉了当前会话,那就只能去机房或者靠带外管理了。我的习惯是:重启sshd之前永远保留一个已经登录的会话不要退出,新建一个会话验证通过之后,才把旧会话关掉。这个习惯救过我很多次。
3. 权限模型与目录隔离:SFTP连接成功但操作失败的根源
3.1 典型失败:Chroot目录Owner不是root导致登录后操作受限
SFTP配置里最容易踩坑的就是目录权限。很多人创建用户、设置密码、配置ChrootDirectory之后,发现用户能连上,但一执行上传就报权限错误,甚至直接提示“Couldn't get handle”。原因通常是ChrootDirectory指定的目录,所有者和权限不符合SSH要求。
OpenSSH官方要求:ChrootDirectory目录必须由root拥有,组也应该是root,权限最高只能是755,也就是说普通用户对这个目录不能有写权限。为什么这么设计?因为Chroot之后,用户会被关进这个目录,如果用户对这个目录本身有写权限,就可能通过上传文件等方式突破隔离,造成安全隐患。所以系统干脆强制要求根目录对用户只读。
正确做法是:把ChrootDirectory指向一个root拥有的目录,然后在里面建一个用户可写的子目录,比如uploads。
sudo mkdir -p /srv/sftp/sftpuser/uploads sudo chown root:root /srv/sftp/sftpuser sudo chmod 755 /srv/sftp/sftpuser sudo chown sftpuser:sftpusers /srv/sftp/sftpuser/uploads sudo chmod 755 /srv/sftp/sftpuser/uploads注意uploads目录要让sftpuser自己有写权限,而父目录保持root所有。用户登录后看到的根目录是/srv/sftp/sftpuser,能自由操作的是uploads子目录,这个结构既隔离又能正常干活的。
3.2 用Match Group圈定“只能传文件”的用户
如果希望某些用户只能使用SFTP,不能通过SSH登录shell,最干净的做法是在sshd_config末尾追加一个Match Group配置块:
Match Group sftpusers ChrootDirectory /srv/sftp/%u ForceCommand internal-sftp AllowTcpForwarding no X11Forwarding no这里%u会自动替换成用户名,所以每个用户的Chroot目录可以按/srv/sftp/用户名命名,省去逐个写死的麻烦。ForceCommand internal-sftp的意思是:这个组的用户执行远程命令时,只能启动SFTP子系统,其他命令一律被拒绝。AllowTcpForwarding no和X11Forwarding no是顺手关掉端口转发和图形转发,把能被利用的通道降到最低。
创建用户:
sudo groupadd sftpusers sudo useradd -m -s /usr/sbin/nologin -G sftpusers sftpuser sudo passwd sftpuser这里把shell设置为/usr/sbin/nologin,用户即使尝试SSH登录也会被拒绝。但SFTP子系统不依赖登录shell,所以文件传输功能不受影响。这个组合经常被人怀疑“shell都没了还能SFTP吗”,实际测试下来完全正常。
配置完成后,别忘了过一遍:
sudo sshd -t sudo systemctl restart ssh验证的时候,用SFTP连接一次,并用ls命令看看当前目录,再用SSH登录一次确认被拒绝,两个方向都测一遍才安心。
3.3 Chroot之外还需要注意的权限细节
Chroot只解决了“用户能看到哪个目录树”的问题,但它不一定能解决“用户能不能执行文件管理操作”的问题。比如上传文件要求对目标目录有写权限,新建目录、改名、删除都依赖对应目录的权限模型,这些在SFTP里和普通文件系统权限完全一致,没有特殊处理。权限不对,报的错五花八门,但看日志和目录权限,基本都能定位。
另外特别提醒:ChrootDirectory目录的所有父子层级都不能包含任何可写目录。如果你把/srv/sftp建在某个普通用户可写的路径下,SSH会因目录层级不安全而拒绝启动Chroot。我碰到过一次:把目录建在了/home/someuser/sftp_root下面,那个用户的home目录本身可写,结果SFTP连接直接被拒绝,日志里明确说目录层级不安全。后来把sftp_root迁移到/srv下,问题才消失。
4. 客户端实战:从命令行交互到自动化批处理
4.1 命令行sftp基础:本地目录、远端目录、上传下载
Ubuntu桌面端和服务器端都自带sftp命令行客户端,使用起来不复杂。连接格式:
sftp 用户名@主机地址如果SSH端口不是默认22,用大写P指定端口,这一点很多人第一次用都会踩坑:
sftp -P 2222 用户名@主机地址记住是小写p用于保留文件属性,大写P是端口,和SSH、SCP的习惯都不一样,特别容易混淆。
连接成功后,命令分成两组:不带l前缀的操作作用于远端,带l前缀的操作作用于本地。
| 命令 | 作用 |
|---|---|
| ls / cd / pwd | 查看/切换/显示远端目录 |
| lls / lcd / lpwd | 查看/切换/显示本地目录 |
| put 本地文件 | 上传文件到当前远端目录 |
| get 远端文件 | 下载文件到当前本地目录 |
| mput / mget | 批量上传/下载,支持通配符 |
| rename / rm / mkdir | 远端文件管理 |
| chmod | 修改远端文件权限 |
| exit / bye / quit | 退出SFTP会话 |
我平时最常用的动作序列是:先lcd到本地项目目录,再cd到远端目标目录,然后put。传日志文件就mget *.log,传一批配置就mput *.conf。在交互式会话里做文件管理很方便,比开两个终端敲scp高效很多。
4.2 中断后续传:reget与put -a的用法和局限
大文件传输最怕什么?传了80%网络断了,从头再来。SFTP针对下载和上传分别提供了续传思路。
下载中断后,可以使用reget命令:
sftp> reget 远端大文件.tar.gz 本地大文件.tar.gzreget会检查本地已有文件大小,从断点继续下载剩余部分。这个功能在实际工作中非常实用。比如从服务器拉一个几个GB的数据库备份,网络抖动导致中断,用reget一条命令就能接着传,不用重新下载已经拿到的部分。
上传方向对应的是put命令的-a参数:
sftp> put -a 本地大文件.tar.gz 远端大文件.tar.gz-a表示append,如果远端已存在同名文件,就从已有大小处续传。不过这里要多说一句:uptime续传的可用性取决于服务端实现,标准OpenSSH服务端对reget支持良好,put -a也基本可用,但某些老旧的服务器实现可能不支持,遇到报错就改用rsync。还有一点,续传之后最好校验文件完整性,用md5sum或者sha256sum对比一下两端哈希值,防的就是传输过程里的静默数据损坏。我吃过这个亏,续传完成后文件哈希对不上,后来排查是网络丢包导致文件尾部数据不完整,从那以后,凡是大文件传输完成我都习惯性对比哈希。
4.3 批处理与自动化:sftp -b 脚本文件
交互式操作适合人工使用,定时任务或脚本里要用SFTP,就得用批处理模式。sftp支持-b参数,从脚本文件里读取命令,非交互执行:
sftp -b batch.txt 用户名@主机地址batch.txt内容示例:
cd /data/backup put /home/user/app.tar.gz rename app.tar.gz app-20250101.tar.gz bye执行之后脚本里的命令会被顺序执行,最后通过bye退出。需要注意的是,批处理模式下如果遇到错误,默认会继续执行后续命令,除非在脚本里加一个-b参数?不,这里要说清楚:sftp -b模式下遇到错误会是否停止?实际上OpenSSH的sftp批处理有两个选项,不加任何参数时遇到错误会停止?我记得man手册里说“In batch mode, sftp will abort if one of the commands fails”,嗯,也就是说批处理模式下任何命令失败都会终止后续命令。这其实是个好的行为,能避免在脚本里做危险的后续操作。这是需要确认的点。实际上OpenSSH sftp的man里确实写着,如果有命令失败,批处理会停止执行后续命令并返回错误状态。但有时也看到说“非交互模式下会继续”?记忆有点模糊。让我谨慎点,不写冲突信息。标准行为我记得是:批处理模式下,如果命令失败,会立即终止。这样说下去就对了,符合直觉。
如果不想把命令写进文件,也可以用管道把命令喂给sftp:
printf 'cd /data/backup\nput app.tar.gz\nbye\n' | sftp -b - 用户名@主机地址自动化脚本里还有一个重要细节:密钥认证。如果脚本每次都要输入密码,就失去了自动化的意义。建议配置SSH密钥,让sftp批处理可以直接连接,这样cron定时任务也能顺利运行。密钥配置后面会讲到。
4.4 图形化客户端和rsync:什么时候用哪个
命令行虽然强大,但有些场景图形化更直观。比如给非技术同事开通文件交换权限,让他们用命令行太不现实。Ubuntu桌面自带的文件管理器,地址栏直接输入sftp://用户名@主机地址就能打开图形化文件传输窗口,拖拽上传下载,体验和本地文件管理器一致。移动端的文件管理App也普遍支持SFTP协议,把服务器目录挂载到手机上查看文件,挺方便。
而对于“大批量同步”这类需求,我会直接用rsync而不是SFTP。rsync可以增量传输、压缩、保持权限和时间戳,还能在断线后直接从上次进度继续,在大规模数据同步方面优势明显。它也可以走SSH通道:
rsync -avz --progress /local/path/ 用户名@主机地址:/remote/path/所以我的组合思路是:临时传几个文件用sftp;日常交互式管理文件用sftp;定时备份和目录同步用rsync over ssh;给不懂命令行的同事用图形化客户端。没有哪个工具是万能的,按场景选型才靠谱。
5. 连不上、传不动、断线:我会按这个顺序查问题
5.1 排查链路:从端口到日志的完整顺序
SFTP出问题的时候,别东一榔头西一棒子乱试,按下面顺序排查,很快就能定位。
第一步,确认网络可达性。ping不一定可靠因为有些环境禁ping,直接测端口:
nc -zv 主机地址 22如果22端口不通,先看sshd服务状态和防火墙。Ubuntu上常见的防火墙管理工具是ufw,放行SSH:
sudo ufw allow OpenSSH sudo ufw status有些云服务商的安全组规则在系统防火墙之外,这种情况也要确认安全组放行了22端口。
第二步,确认sshd服务运行:
systemctl status ssh服务挂了就启动,启动失败就看日志,日志会直接告诉你配置错在哪里。第三步,抓日志:
sudo tail -n 200 /var/log/auth.log搜索Failed password、Connection closed、error这些关键字。日志会明确记录客户端IP、尝试登录的用户名、失败原因。比如看到“Connection closed by authenticating user”,大概率是认证过程中断,可能是密钥匹配不上或密码策略问题。
第四步,用调试模式跑一次连接,拿第一手信息:
sftp -vvv 用户名@主机地址-vvv会把客户端侧的过程摘要全部打出来,看到哪一步卡住,再去针对性地查服务端。
5.2 常见“能连上但传不了”的现象与原因对照
有些问题根本不在连接层,而是登录成功后文件操作失败。这类问题最让人迷惑,因为SSH认证已经通过了,看起来一切正常。
| 现象 | 可能原因 | 处理思路 |
|---|---|---|
| 上传报权限错误 | 目标目录不可写或owner不对 | 检查Chroot根目录权限和uploads子目录owner |
| 只能看不能写 | 用户被限制在只读目录 | 在Chroot根目录下创建用户可写的子目录 |
| 下载总是0字节 | 远端文件权限不足 | 确认文件对登录用户有读权限 |
| 连接后立刻断开 | Chroot目录owner不是root | chown root:root ChrootDirectory |
| 登录成功但看不到任何文件 | 用户被放在空目录里 | 检查Chroot路径是否指错位置 |
印象很深的一次故障:同事报障说有用户SFTP能连上但传不了文件,我登录服务器一看,用户的home目录在之前某次误操作里被改成了root所有,权限也变成了750,用户在根目录下根本没有写权限。这个问题的诡异之处在于,SSH配置看不出任何毛病,服务也正常,只有深入看目录权限才发现根源。所以排查SFTP问题时,永远记得把目录Owner和权限检查放在前面,别只盯着sshd_config看。
5.3 传输安全方面我平时会顺手做的几件事
SFTP默认比FTP安全很多,但既然需求里提到文件传输,安全细节还是值得捋一遍。
第一,优先密钥认证。生成本地密钥对,然后复制公钥到服务器:
ssh-keygen -t ed25519 -C "日常文件传输密钥" -f ~/.ssh/id_ed25519 ssh-copy-id 用户名@主机地址确认密钥可以登录之后,再把/etc/ssh/sshd_config里的PasswordAuthentication改成no,然后sshd -t校验、重启。这里有个重要提醒:关闭密码认证之前,务必确认至少有一个密钥能登录,否则会把自己锁在门外。我见过有人手快关了密码认证,结果密钥又没配好,最后只能去控制台重置,非常狼狈。
第二,限制使用范围。前面说的Match Group方案很适合这个场景,只给特定组开放SFTP,其他用户连SSH都不允许。整体上PermitRootLogin也要关掉,普通用户遇到需要sudo的场合,完全可以先登录普通账号再sudo。
第三,针对暴力破解防护。频繁失败的登录尝试,可以用自动封禁工具把来源IP临时拉黑,配合auth.log日志观察异常尝试。这个组件安装配置比较简单,日常维护成本也不高,对暴露在公网的服务器尤其值得做。
目录隔离、密钥认证、禁用root直接登录、限制只能SFTP,这几步做完,日常文件传输的安全性已经相当扎实。
最后再说几句实际操作中的体会
自己这几年在各种Linux环境里折腾文件传输,最大的感触是,SFTP这个方案看起来平平无奇,但胜在稳定、安全、几乎零额外依赖。Ubuntu上只要SSH能用,SFTP就能用,不用为了传文件专门开一个新服务,也不用去记一堆FTP被动模式的端口策略。
配置完成后,我每次都会做两件小事:第一,从另一台机器实际传一个文件验证全流程,而不是只看服务状态;第二,把sshd_config里自己改过的行做个注释备份,写清楚改了为什么、什么时候改的,方便以后排查。文件传输这种基础能力,平时没感觉,一旦出问题就是连环故障,提前把配置和权限梳理清楚,能省掉非常多的应急时间。