我们工作中经常要在Ubuntu上搞定机器与机器、机器与人的文件传递。以前我习惯用FTP,后来吃过几次被动模式连不上的亏,转过SCP,又发现它每次传目录都得吃药一样重复敲参数。绕了一圈之后,我稳定用SFTP作为主力方案,在Ubuntu系统里传文件,它既不额外开端口,也不用单独起一套服务,只要SSH是活的,文件传输通道就一起带上了。这篇就专门把SFTP在Ubuntu下的使用方式、配置细节和我踩过的坑整理出来。
先说清楚适合谁看:刚接触Linux、想把文件在本地和服务器之间搬来搬去的同学,能在半小时内搭出一套能用的传输通道;已经会基本操作但想规范权限、做自动化脚本的运维朋友,也能从后半段的配置和排查记录里找到一些参考。
1. 选型之前的几个判断:为什么SFTP值得当主力
1.1 SFTP不是FTP加个S,别再被名字误导
很多人第一次看到SFTP,都以为它就是把FTP协议外面包了一层TLS/SSL,跟HTTPS和HTTP的关系一样。这个理解是错的。SFTP全名是SSH File Transfer Protocol,注意关键词是SSH。它跟传统FTP是两个完全不同的协议,只是名字撞车了。
传统FTP走21号端口传指令,然后用20号端口或者随机高端口传数据。这就带来一个很现实的问题:只要中间有防火墙,FTP的主动/被动模式就很容易翻车,要么数据端口被挡,要么客户端模式切错了传不动。另外FTP的账号密码默认是明文在网络里跑,抓包工具一开,账号密码直接裸奔。我在内网见过不止一次因为FTP明文密码被嗅探导致服务器被登录的情况。
SFTP完全不同。它建立在SSH连接之上,默认走22端口,所有指令、认证、数据流量都在同一条加密通道里传输。不需要额外开数据端口,不存在主动被动模式的说法,防火墙策略只需要放行SSH这一个端口即可。更关键的是,它能复用系统已有的SSH用户体系,不需要像FTP那样单独管理一套虚拟账号。
1.2 和SCP、rsync放在一起比,差异比想象中更大
经常被人拿来对比的概念还有SCP和rsync,我干脆把它们放一张表里看,方便取舍。
| 对比项 | SFTP | SCP | rsync(SSH模式) |
|---|---|---|---|
| 基于SSH | 是 | 是 | 可通过SSH隧道 |
| 交互式目录浏览 | 支持 | 不支持 | 不支持 |
| 断点续传 | 新版支持reget/reput | 不支持 | 支持 |
| 目录递归传输 | 支持 | 支持 | 支持,且增量 |
| 文件列表操作(删、改名、建目录) | 支持 | 不支持 | 有限支持 |
| 自动化脚本友好度 | 较高,批处理方便 | 简单直接 | 增量同步更高效 |
| 最典型场景 | 交互式管理远端文件 | 快速拷单个文件 | 目录级增量同步 |
SCP胜在简单,一条命令传完就走,适合脚本里快速搬运单个文件。但它不能列出远端目录、不能执行远端删除改名,而且传输中断之后只能重新来。rsync强在大目录增量同步,但日常"连上去看看服务器上有哪些文件、挨个目录拉下来"这种交互操作,rsync完全做不了。SFTP把这些都揉在了一起:既能像FTP客户端那样交互浏览,又能写脚本批处理,还不需要额外交付。
1.3 我实际用它解决的几类问题
SFTP最常见的落地场景有这么几种,我自己的使用经历基本都覆盖了:
- 服务器之间传部署包,不需要在目标机上额外安装FTP服务端;
- 帮同事把公网服务器上的日志拉回本地分析,只需要给一把只读账号,不用开放整个服务器SSH权限;
- 在脚本里定时从远端目录下载报表文件,用批处理参数执行sftp命令,配合cron跑得挺稳;
- 需要临时给外部人员一个目录让他们上传文件,用chroot把对方限制在一个固定目录里,安全边界可控。
这些场景里,SFTP都能用一套SSH体系解决,省心之处在于不用记住多套端口、多套协议规则。内网越是复杂、防火墙越严格,这种"只开一个端口"的方案就越有优势。
2. 环境准备与服务端配置:先把传输通道搭起来
2.1 确认SSH服务状态
SFTP是SSH服务的一个子系统,所以前置条件只有一个:目标Ubuntu机器上运行着sshd服务。先检查系统里有没有这个服务在跑:
systemctl status ssh看到 active (running) 就直接跳到下一步。如果提示找不到服务,就是没装OpenSSH Server,安装很轻量:
sudo apt update sudo apt install openssh-server装完顺手设成开机自启:
sudo systemctl enable --now ssh2.2 确认sftp子系统已启用
SSH服务默认自带SFTP子系统配置,位于 /etc/ssh/sshd_config 中:
Subsystem sftp internal-sftp这一行默认是存在的。它表示SSH服务自带的内部SFTP实现,不依赖外部的sftp-server二进制文件。我建议保留 internal-sftp 而不是外部的 /usr/lib/openssh/sftp-server,前者对chroot的支持更自然,后面配置权限限制时省事。
改完任何sshd_config配置之后都要重载服务:
sudo systemctl reload ssh这里注意是reload而不是restart。reload只是让sshd重新读配置,不会断掉当前已经建立的SSH会话;restart会断开全部连接,如果你正在通过SSH去远程改配置,restart有可能让自己掉线。
2.3 最小可用配置建议
需要连SFTP的账号可以是普通系统用户,也可以是已有的root用户。但我不建议直接拿root账号跑日常文件传输,权限太大,误操作风险高。
建议的做法是新建一个专用账号:
sudo useradd -m -s /usr/sbin/nologin filetransfer sudo passwd filetransfer把shell设成 /usr/sbin/nologin 是让这个账号无法登录SSH执行命令,只能用来传文件。配合SFTP的ChrootDirectory设置,等于把对方锁在一个目录里,只能在这块隔离区里活动。具体配置在第4节展开。
权限设计上记住一个核心原则:传输文件的目录属主最好归实际使用人,不要直接chmod 777。我看到很多教程让人把目录改成777解决写入问题,这是典型的饮鸩止渴。正确做法是让目录属主和用户匹配,必要时在组权限上放开写权限就够用了。
3. 客户端实操:命令行、脚本和图形化工具
3.1 交互模式的核心命令
命令行sftp是Ubuntu自带的客户端,最朴素也最可靠。基本用法:
sftp username@server_ip如果SSH不是默认22端口,用-P指定:
sftp -P 2222 username@server_ip连进去后就是sftp>提示符交互界面。常用命令分成两类:
远端操作命令:
- pwd——查看远端当前目录
- ls——列出远端目录文件,支持-l等参数
- cd——切换远端目录
- get 远端文件 [本地路径]——下载文件
- put 本地文件 [远端路径]——上传文件
- mget/mput——批量下载/上传,支持通配符
- mkdir/rmdir/rm/rename——执行远端目录文件管理
- ln——创建远端软链接
本地操作命令需要在前面加l前缀:
- lpwd——查看本地当前目录
- lls——列出本地目录
- lcd——切换本地目录
- ! 命令——执行任意本地shell命令
最常用的上传下载方式是这样的:
sftp> lcd /home/user/deploy_pkg sftp> put app_v2.0.tar.gz /data/backup/ sftp> get /data/logs/error.log ./error_202405.log传大目录可以直接-R递归:
sftp> get -R /data/www /home/user/www_backup这里要注意,get和put都支持在命令里直接写目标端路径,不用非得先cd过去再操作,养成这个习惯能少敲很多命令。
3.2 非交互与批处理模式:脚本自动化的基础
交互模式适合人工处理,但要做定时传输的话就得用批处理文件。把要执行的sftp命令写进一个文件:
lcd /home/user/export cd /remote/data/import put daily_report.csv bye然后这样执行:
sftp -b batch_file username@server_ip有些版本对-b参数有约束:批处理模式一旦出错就退出,不会继续执行后面的命令。如果希望忽略单个错误继续跑,可以加参数:
sftp -b - username@server_ip <<EOF put file1.txt put file2.txt quit EOF实测下来,用heredoc方式比-b文件更灵活,因为可以在shell脚本里动态拼接命令列表。但无论是哪种自动化方式,密码处理都是个大坑。sftp不能直接在命令行里写密码,指望脚本里echo密码进去是不行的。要自动化就必须配SSH密钥,步骤在3.4节。
3.3 图形化工具的配置要点
内网用户不习惯命令行时,我常用FileZilla这类SFTP客户端帮他们装好、配好连接。需要注意的坑是:连接协议要选"SFTP - SSH File Transfer Protocol",不是普通的FTP。端口默认22。主机地址、用户名、密码填好,第一次连会弹SSH主机指纹确认,核对指纹无误后选择信任即可。
如果连接报了"服务器不支持的协议"之类的错,大概率是用户在客户端选了FTP协议。这个错我在帮人排查时遇到过好几次,看着像服务器配置有问题,实际就是客户端选错协议。
另外FileZilla支持把私钥文件直接作为认证方式,连接时选择"密钥文件",就能省去密码输入。使用Ed25519或RSA私钥都可以。
3.4 密钥认证配置,把密码这一步彻底去掉
密钥登录是SFTP自动化的基础,也是安全加固的重要一步。在客户端生成密钥对:
ssh-keygen -t ed25519 -C "file-transfer-comment"一路回车即可,默认生成 ~/.ssh/id_ed25519(私钥)和 ~/.ssh/id_ed25519.pub(公钥)。然后把公钥推送到服务器上:
ssh-copy-id -i ~/.ssh/id_ed25519.pub filetransfer@server_ip这条命令会自动把公钥追加到远端用户主目录的 ~/.ssh/authorized_keys 文件中。
没有ssh-copy-id的时候可以手动创建:
mkdir -p ~/.ssh && chmod 700 ~/.ssh touch ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys echo "公钥内容" >> ~/.ssh/authorized_keys权限是关键。SSH对密钥文件权限有硬性检查:~/.ssh目录不能是其他用户可写,authorized_keys文件权限不能超过600。如果配好密钥仍然提示要密码,检查一下是不是authorized_keys的权限被改到了644,这是一个非常容易踩的坑。
4. 权限限制、日志审计与传输体验优化
4.1 ChrootDirectory:把用户关在指定目录里
SFTP做文件交付时,最有价值的安全功能是ChrootDirectory。它能将一个SFTP用户限制在指定目录中,用户看不到目录之外的任何文件,也不能跳出这个范围。
在 /etc/ssh/sshd_config 的末尾加一段专属配置:
Match Group sftpusers ChrootDirectory /data/sftp/%u ForceCommand internal-sftp X11Forwarding no AllowTcpForwarding no PasswordAuthentication yes然后创建目录并设置权限:
sudo mkdir -p /data/sftp/uploader sudo chown root:root /data/sftp/uploader sudo mkdir -p /data/sftp/uploader/incoming sudo chown uploader:uploader /data/sftp/uploader/incoming sudo usermod -aG sftpusers uploader这里有个非常隐蔽的细节:ChrootDirectory指向的目录本身必须属于root且权限为755,否则用户会直接连不上,日志里报 connection closed。因为chroot后用户需要进入这个目录,但该目录的属主不能是普通用户自己,否则存在越权风险。我在第一次配置时在这里卡了一个小时,后来才发现是目录属主不对。
目录架构建议建一个incoming子目录给用户写入,这样对方能上传但看不到系统其它部分,下载区单独放一个目录,读写分离结构更清晰。
4.2 日志怎么看:追踪连接与传输痕迹
排查SFTP问题,最直接的办法是看SSH日志。Ubuntu系统使用systemd之后,认证日志集中在 /var/log/auth.log,可以直接看,也可以用journalctl:
sudo journalctl -u ssh --since "today"常见的有用信息包括:
- Accepted password for xxx——密码登录成功
- Accepted publickey for xxx——密钥登录成功
- Failed password——密码错误
- userauth request for invalid user——错误用户名尝试
- subsystem request for sftp by user xxx——用户已经请求SFTP子系统
传输文件本身的路径、文件名、大小一般不会记录在auth.log里,需要进一步配置SFTP自身的日志功能。但至少连接结果、认证方式、来源IP这些信息是有的。要做严格审计的,可以配合auditd监控被传输目录的读写变化,这个我后续有机会再单独写。
4.3 传输速度与稳定性的实战调优
SFTP传输慢,很多人第一反应是加密算法拖累性能,实际上绝大多数场景瓶颈不在这里。以下几个原因优先级更高:
机器之间网络质量差、延迟高时,SFTP的单流传输会受限。传统TCP单连接在长肥网络(高带宽高延迟)环境里跑不满带宽,这是TCP本身的窗口机制决定的。要跑满带宽,要么调大缓冲区,要么多开几个连接并发传输。
在sftp命令行里能做的调整之一是加大读写缓冲:
sftp -B 65536 username@server_ip默认缓冲区较小,加大到64KB以上对跨地域传输有帮助。如果传输的是大量小文件,瓶颈更多在往返延迟和目录操作的每秒次数上,这时候建议先把文件打包成tar.gz再传,性能提升非常明显。
还有一个隐藏点:如果服务端CPU太老,也会导致加解密慢。可以在sshd_config里限定使用更快的高性能算法组,但我不建议为了性能牺牲安全性,默认算法就够了。
连接稳定性方面,常遇到长时间挂着的SFTP会话被中间设备掐断。命令行参数有对应的保活手段:
sftp -o ServerAliveInterval=30 -o ServerAliveCountMax=3 username@server_ip意思是每30秒发一个心跳包,连续3个回包没收到就断开。这样既保持会话不被闲置掐断,又能在远端真死机时及时归还资源。
5. 日常问题排查与避坑经验
5.1 连不上:连接被拒绝的排查顺序
SFTP登录失败时的报错五花八门,我按出现频率排一下排查顺序:
先确认SSH服务还活着:
systemctl status ssh服务没问题的话,确认端口:
ss -lnpt | grep sshd接着确认防火墙有没有放行22端口:
sudo ufw status如果是云服务器,还要检查安全组是否放行了对应端口。我经历过一次最诡异的情况:本地nc测端口通,但SFTP就是连接超时,后来发现是安全组只放行了TCP 22,但客户端走了IPv6地址,IPv6的流量被安全组丢弃了。这种问题用套接字统计根本看不出来,必须用tcpdump抓包或者关掉IPv6才能定位。
5.2 登录成功但传不了文件:目录权限问题
登录进去,get和ls都正常,一put就报Permission denied,这个问题十有八九是目标目录的写权限不对。
解决思路:
ls -ld /data/sftp/uploader确认当前登录用户对该目录是否有写权限。注意设置属主和组权限时,不要图省事直接chmod 777,稍微规范一点的用法是把目录属主改为用户,或者把用户加入目录所属的组:
sudo chown uploader:uploader /data/sftp/uploader/incoming sudo chmod 775 /data/sftp/uploader/incoming同样,下载时提示Permission denied,往往是因为文件权限是600且属主是root。要么用sudo下载,要么通过ACL给用户增加读权限。我自己的习惯是给只读账号所在的组开放读权限,既不让对方有写权限,又保证能拿到文件。
5.3 文件名和内容乱码的两类不同原因
乱码问题得先分清是内容乱码还是文件名乱码。
内容乱码多数是文本文件编码不一致,比如远端生成的是GBK编码报表,本地终端默认按UTF-8解析,就会显示乱码。这个跟SFTP无关,处理时用iconv转码即可:
iconv -f GBK -t UTF-8 error.log > error_utf8.log它本质上是字节流,不属于传输层面的问题。
文件名乱码则可能跟字符集有关。Linux下文件名是UTF-8编码,如果Windows客户端用本地字符集去显示,就可能出现中文名变问号。这个属于客户端显示问题,在FileZilla里设置强制UTF-8编码可以缓解。如果乱码发生在服务端,检查一下文件系统挂载的字符集设置,特别是老旧的FAT格式挂载磁盘传输文件时容易出问题。
5.4 自动化脚本里常见的三个坑
做定时SFTP传输时,踩得最多的坑是这三个:
第一,脚本里用echo密码或expect自动应答密码,这是最不推荐的。明文密码会留在shell历史记录里,换密码后脚本还会挂,而且要额外装expect。正确做法是密钥配对,5分钟能配完,一劳永逸。
第二,批处理模式遇到单个文件失败就整体退出。例如远端磁盘满了,某个文件传不进去,整个脚本后面的文件全部不会执行。可以在脚本里给每个文件单独调用sftp,或者用heredoc方式配合判断,至少能让脚本在单个任务失败后仍能继续跑。
第三,服务器端ssh主机指纹变更后,脚本会突然报Host key verification failed。这种情况多半是服务器重装过系统,或sshd host key被重新生成。处理方式是更新客户端known_hosts中的记录:
ssh-keygen -R server_ip重新连接一次,让指纹重新写入known_hosts即可。自动化任务里最好提前监控到这个错误,不然半夜里任务断了都不知道。
6. 再补几个实操中的小习惯
使用SFTP久了之后,我养成了一些固定习惯,代码里敲起来也很顺:
需要传大量小文件时,先tar打包再传,速度差距可以在10倍以上。示例:
tar czf logs_archive.tar.gz /var/log/myapp/ sftp> put logs_archive.tar.gz传输前先检查远端磁盘空间,避免传一半发现磁盘满了:
sftp> df -h这会显示远端的磁盘使用情况,比登录进去再执行df快点。
在脚本中使用sftp时,尽量在命令尾部加退出状态判断。sftp自身返回值存在一定局限性,一个稳健的做法是,在远端执行文件大小核对命令,把结果带回本地和源文件比对,确保长度一致后再认为传输成功。
最后再分享一个我在实际项目中反复调整后确定的方案:给每个需要对外传输文件的用户建独立目录,配合chroot锁死边界,认证只用密钥,密码认证全部禁用。这样运维我只需要知道服务器IP和用户的公钥,不需要在群里传来传去换密码。这套模式我用下来,文件传输这件事基本没有再来找过我了。