☰
scp命令详解:借助SSH隧道实现Linux远程文件传输与自动化
2026/10/23 3:21:53 网站建设 项目流程

接手一台只有SSH访问权的服务器时,最先被卡住的往往不是服务配置,而是文件搬运。很多人已经习惯了“先SSH登录上去再想下一步”,可一旦需要把本地一个压缩包传到远程,或者从远程拿一份日志下来,就不知道从哪下手了。我最近帮一位开发同学排查问题,他就是已经成功登上了服务器,却一直在问“怎么把文件弄上去”,绕了很大一圈才想起还有scp这个东西。这个场景其实特别典型:SSH连接容易被当成一个登录入口,却很少有人意识到,SSH本身就是一条现成的加密传输通道,而scp正是利用这条通道来搬运文件的工具。

这篇笔记会把scp从基本用法讲到实战踩坑,内容包括四类最常用的命令形态、免密钥配置与脚本自动化、常见报错的完整排查链路,以及限速、压缩、断点续传等进阶需求下怎么和rsync配合。无论你是刚接触Linux的初学者,还是要管理多台服务器、经常需要远程传文件的运维人员,应该都能从这里找到可以直接照搬的操作。

1. scp到底是什么:它只依赖SSH这条隧道

scp的全称是secure copy,几乎所有Linux发行版都会随openssh-clients软件包一起装上。它和SSH共享同一套底层协议,也就是说,scp并不是一个独立的文件传输协议,而是“借SSH的通道做复制”。当你执行一条scp命令时,本地进程通过SSH加密隧道连接远程的sshd服务,远程sshd会派生一个scp进程来配合接收或发送数据。形象点说,SSH是在两台机器之间打了一条加密隧道,scp就是在隧道两头来回搬货的人。

理解这一点非常重要,因为它直接解释了为什么scp用起来这么“省事”:你不需要在目标服务器上单独安装什么scp服务端、不需要额外开放端口,也不需要像FTP那样维护一套用户口令库。只要当前这台机器能用SSH连上,那它大概率就能用scp传文件。反过来,如果SSH本身就连不上,scp也必然失败,因为两者依赖的是同一个连接通道。

初学者容易把scp和FTP混为一谈,因为它们都在做“传文件”这件事,但架构差别很大。FTP通常需要在服务器上单独启动服务,监听21端口,认证和传输走的是独立协议;而scp走的是SSH的22端口,认证方式完全复用SSH的密码或密钥体系。最直观的验证方式就是:你配置好了SSH免密钥登录,那么scp也会自动跟着能够免密钥传输,不需要再做任何额外设置。

有些刚接触服务器的人还会问,scp是不是和FTP一样也有独立的“SCP服务端”需要开启。答案是不用。远程机器上只需要有sshd在运行,当scp命令发过去时,sshd会临时派生一个进程来处理文件流。这也是为什么在生产环境里,我们经常说“SSH能用,scp就能用;SSH不能用,scp想都别想”。

另外值得强调的是,scp和SFTP也经常被搞混。SFTP虽然是另一个协议,但它同样跑在SSH通道上,也复用SSH认证,只不过SFTP更强调交互式文件管理、断点续传和远程目录浏览。用一句话概括这几种工具的差异:“scp更适合一条命令完成的一次性复制,SFTP更适合交互式浏览和搬运,FTP则是另一套独立且相对过时的体系,远不如SSH这套直接。”

2. 四种最常用的scp命令形态:先把上传下载练熟

scp的命令格式远看像cp,远看又不完全是,因为它多了一个“远程主机标记”,也就是冒号。冒号前的部分属于本机或远程主机,冒号后的部分是对应主机上的路径,必须把这条分界线记清楚,命令才不会写乱。

2.1 从本地上传到远程

最基础的上传命令长这样:

scp /data/app.tar.gz user@192.0.2.10:/home/user/uploads/

这段命令的意思是:把本机的/data/app.tar.gz复制到远程主机192.0.2.10上,远程用户名是user,目标目录是/home/user/uploads/。如果目标目录写错了,比如路径不存在,scp会直接提示No such file or directory,而且它不会像某些工具那样层层帮你创建目录。所以一个很实用的习惯是:先SSH上去用mkdir -p把目录建好,再回来执行scp。

2.2 从远程下载到本地

方向反过来就是:

scp user@192.0.2.10:/home/user/uploads/app.tar.gz /data/

这条命令会把远程机器上的app.tar.gz拉到本地的/data/目录,文件名保持不变。你也可以在本地路径里手动指定一个新名字,比如:

scp user@192.0.2.10:/home/user/uploads/app.tar.gz /data/app-backup.tar.gz

下载和上传的核心区别只是“远程路径写在前面还是后面”,理解了冒号两侧分属哪台机器,方向基本不会错。

2.3 非22端口时用大写-P

如果远程SSH端口不是标准的22,比如改成了2222,上传命令要写成:

scp -P 2222 /data/app.tar.gz user@192.0.2.10:/home/user/

这里的-P必须大写,而SSH命令指定端口时用的是小写-p。之所以容易搞混,是因为scp的小写-p被用来表示“保留文件属性”了,端口参数只能换成大写。这个细节几乎是我见过踩坑率最高的点之一,写脚本时尤其要盯紧。

2.4 复制目录必须加-r

scp不会默认递归复制目录。你直接scp ./dist user@host:/path/大概率会收到一个提示,告诉你dist不是普通文件。正确写法是:

scp -r ./dist user@192.0.2.10:/var/www/html/

但这里有一个语义陷阱:执行完上面的命令后,远程并不是把dist里的内容直接铺到/var/www/html/下面,而是会生成/var/www/html/dist目录,把内容放进去。如果你想要的是一开始就把dist内的所有文件散落到目标目录,比如把构建产物直接推到Web根目录,你需要写成:

scp -r ./dist/* user@192.0.2.10:/var/www/html/

再用通配符的方式把目录里的内容展开复制。不过这个写法有个附带问题:以点开头的隐藏文件大多不会匹配到*,所以如果目录里有隐藏配置文件要一同分发,用rsync更稳。

2.5 常用参数速查

参数含义示例
-P指定远程SSH端口,大写scp -P 2222 file user@host:/tmp/
-p保留修改时间和访问时间,小写scp -p file user@host:/tmp/
-r递归复制目录scp -r ./conf user@host:/opt/
-C开启压缩传输scp -C log.txt user@host:/tmp/
-l限制带宽,单位是Kbit/sscp -l 8192 file user@host:/tmp/
-i指定私钥文件scp -i ~/.ssh/deploy key ...
-o透传SSH连接参数scp -o ConnectTimeout=10 ...

这些参数里,-l的单位是个经典陷阱,后面在实战章节我会专门解释。

3. 免密钥配置与脚本自动化:让scp不再每次问密码

scp每次执行都要走SSH登录认证,默认情况下会询问密码。手动传一次两次还好,可一旦要在脚本里批量分发文件,交互式输密码就没法接受了。所以真正让scp变得好用起来的,是SSH密钥对的配置。

3.1 生成一对密钥

在一台机器上生成新的密钥对,推荐用ed25519算法:

ssh-keygen -t ed25519 -a 100 -f ~/.ssh/deploy_key

ed25519密钥短、生成快、安全性高,主流Linux发行版的新版OpenSSH都支持。如果内网里有一些老旧的系统不支持ed25519,那就退回rsa:

ssh-keygen -t rsa -b 4096 -f ~/.ssh/deploy_key

生成密钥对的过程中可以设置口令,也可以直接留空。脚本自动化场景一般留空,但私钥文件一定要保管好,权限设置成只有自己能读。

3.2 把公钥装到远程

用ssh-copy-id一条命令装过去:

ssh-copy-id -i ~/.ssh/deploy_key.pub user@192.0.2.10

它会自动把公钥追加到远程用户的~/.ssh/authorized_keys文件里,并处理好目录权限。如果远程SSH端口不是22,需要给ssh-copy-id传对应参数,具体可以看它的帮助信息。装完之后,直接执行一次:

ssh -i ~/.ssh/deploy_key user@192.0.2.10 "echo ok"

能返回ok,就说明免密钥登录已经生效。接下来scp也会跟着免密钥传输,因为scp和SSH共用认证。

3.3 指定密钥执行scp

免密钥配置好之后,scp命令指定私钥即可:

scp -i ~/.ssh/deploy_key ./app.tar.gz user@192.0.2.10:/opt/deploy/

如果你经常操作同一组机器,可以把主机别名、用户名、私钥路径写到~/.ssh/config里,之后命令会短很多。

3.4 脚本里防止交互卡死

自动化的核心要求是“不要卡在交互提示符”。有两个SSH参数特别实用。第一个是BatchMode=yes,它的作用是禁止一切交互式提问;当密钥认证失败时,scp会直接报错退出,而不会停在密码提示符前等半分钟。第二个是StrictHostKeyChecking=accept-new,它会在第一次连接一台新主机时自动接受SSH指纹,避免known_hosts的确认提示卡住流程。

scp -i ~/.ssh/deploy_key \ -o BatchMode=yes \ -o ConnectTimeout=10 \ -o StrictHostKeyChecking=accept-new \ ./app.tar.gz user@192.0.2.10:/opt/deploy/

accept-new这个选项要求OpenSSH版本足够新,如果机器上版本比较老,可以先手动执行一次ssh命令完成指纹记录,脚本里再用StrictHostKeyChecking=no。不过后者会降低安全性,只建议在一次性环境里使用。

3.5 一个完整的部署脚本示例

下面是一个常见的上传并重启服务的脚本骨架:

#!/usr/bin/env bash set -euo pipefail KEY="$HOME/.ssh/deploy_key" HOST="user@192.0.2.10" DEST="/opt/myapp" scp -i "$KEY" \ -o BatchMode=yes \ -o ConnectTimeout=10 \ ./app.tar.gz "$HOST:$DEST/app.tar.gz" ssh -i "$KEY" "$HOST" " cd $DEST && tar xzf app.tar.gz && systemctl restart myapp " echo "部署完成"

这套流程里有个容易被忽略的点:-o BatchMode=yes虽然能防止卡在密码提示,但如果在脚本运行环境里没有配置好密钥,它会直接失败。这其实是好事,自动化任务需要的就是快速失败而不是闷在那里等人工干预。

4. scp报错排查:从现象到根因的完整链路

scp报错信息通常很短,但每条背后都对应着不同的根因。我习惯用一条主线来查:先确认SSH本身能不能连,再确认认证能不能过,最后才看文件权限和传输过程。下面几个是出现频率最高的报错。

4.1 Connection refused

这个报错说明TCP层就没连上。先检查自己写的端口对不对,特别是非标准端口场景下有没有漏掉大写-P。然后本机直接试一次SSH连接,比如:

ssh -p 2222 user@192.0.2.10

如果SSH也报同样错误,说明问题在远程sshd或者防火墙,和scp本身没关系。可能的原因包括sshd服务没启动、端口监听在别的地址、防火墙策略拦截了来源IP。可以用本地的网络排查工具探测一下端口是否可达,如果探测不通,优先去目标机器上看ssh服务的监听状态和防火墙规则。

4.2 Permission denied

这个报错分两种情况。一种是提示Permission denied (publickey,password),代表认证没过。可能是用户名写错了、密码不对、服务器关闭了密码登录,或者公钥没有正确配置。想快速验证密码认证是否被允许,可以强制指定一种认证方式:

ssh -o PreferredAuthentications=password -o PubkeyAuthentication=no user@192.0.2.10

如果这样能连上,说明问题多半出在密钥认证这边。最常见的密钥问题是远程~/.ssh/authorized_keys的权限配置不对,比如.ssh目录权限是777,或者authorized_keys文件权限太开放。OpenSSH对权限非常敏感,一般要求家目录不能组可写,.ssh目录是700,authorized_keys是600。

4.3 Host key verification failed

这条报错说明远程主机的指纹和本机known_hosts里记录的不一样,通常是因为服务器重装过系统、SSH密钥被重新生成,或者远程是一台新机器。解决办法是把旧指纹从本机移除:

ssh-keygen -R 192.0.2.10

然后重新连接。但这里有个安全提醒:删除known_hosts之前,先确认远程机器确实是你认为的那台,否则可能有人在中间伪造主机,不要为了省事就盲目信任新指纹。

4.4 scp提示文件目标路径权限拒绝

有时候SSH能连接,认证也通过,但复制文件时报scp: /opt/xxx/file: Permission denied。这是远程用户对目标目录没有写权限,和scp本身关系不大。解决方式比较直接:先SSH上去看目录属主和权限:

ssh user@192.0.2.10 "ls -ld /opt/xxx"

如果目录属于别的用户,那就换个用户执行scp,或者调整目录权限。scp不会自动帮你改变远程文件属主,这一点在往系统目录传文件时尤其常见。

4.5 传输很慢或中途卡住

先跑一遍带调试信息的命令:

scp -v ./bigfile.tar.gz user@192.0.2.10:/tmp/

-v会输出SSH连接和认证的详细过程。看到debug1: Authentication succeeded说明认证已经通过,后面如果迟迟没有文件传输的进度,重点检查网络质量、带宽占用,以及远程机器的磁盘空间。远程磁盘写满时,scp可能出现传输停滞或者各种奇怪的写入错误,在目标机器上执行df -h能够快速确认。

4.6 调试输出的读法

scp -v的输出看起来很多,但真正有用的就那么几行。连接阶段看Connecting to对应的IP和端口;认证阶段看Authentications that can continue和Authentication succeeded;最后阶段看Sending command: scp -v -t。如果看到了那行命令,说明远程已经接受了请求,开始进入文件流阶段,问题基本就不再是SSH认证层面的了。用这种分段法,几秒钟就能把问题定位到连接、认证还是写入其中的某一环。

5. 限速、压缩、保持属性:scp的进阶细节

基础用法跑通之后,再往深处走就全是细节了。这些细节在常规文档里容易一带而过,但实际用起来差别非常大。

5.1 限制带宽的单位陷阱

scp的-l参数官方文档写的是限制带宽,单位是Kbit/s,也就是千比特每秒。问题恰恰出在这个单位上。很多人以为-l 1024是限到1MB/s,实际算下来只有1024Kbit/s即128KB/s,差距整整八倍。想限制成1MB/s,要写-l 8192:

scp -l 8192 ./bigfile.tar.gz user@192.0.2.10:/data/

这个参数在通过公网向生产服务器传大文件时很有用,不然一次scp可能把出口带宽占满,影响线上其他服务。

5.2 压缩传输的收益边界

-C参数开启传输压缩。如果是日志文件、JSON、未压缩的数据库导出,压缩后字节数能大幅减少,节省传输时间。但如果文件本身已经是.tar.gz、.zip、图片或视频,再压缩几乎没有收益,反而会消耗双方CPU。传一批文本格式的配置文件时,可以放心用:

scp -C *.conf user@192.0.2.10:/etc/backup/

5.3 -p保留时间戳的价值

scp默认不会保留文件修改时间,复制过去后文件mtime会变成传输时刻的时间。这个细节平时不起眼,但如果你后续要做增量同步,或者按时间维度判断文件新旧,就会很麻烦。所以只要场景允许,我都会加上小写-p:

scp -p config.yml user@192.0.2.10:/opt/app/

5.4 scp不支持断点续传

这是scp最明显的能力边界。协议设计相对简单,文件是一次性流式读取的,一旦中断,下次就要完全重传。传一个800MB的文件,传到799MB断掉,几乎能把人气得摔键盘。面对大文件传输,正确做法是改用rsync,利用--partial和--append-verify实现断点续传和完整性校验,这部分在下一章展开。

5.5 批量传文件时尽量不要依赖远程通配符

本地多个文件一次传是很方便:

scp a.txt b.txt c.txt user@192.0.2.10:/tmp/

但通过远程路径直接使用通配符,比如:

scp user@192.0.2.10:/var/log/nginx/*.log ./

这个行为是否稳定,取决于远程shell是否对通配符做了展开、以及scp实现版本对远程通配的处理方式。我遇到过几次目录里文件数量一多、或者文件名带特殊字符时远程通配失效的情况。更稳妥的批量拉取方案是直接用rsync,或者先用一串远程命令把文件列表准备好,再在本地循环拉取。

6. 什么时候该放弃scp:rsync和sftp的补位

scp优势在于简单直接,一行命令就能完成复制。但它也有明确短板:不支持增量同步、不支持断点续传、不会删除目标端多余文件、目录复制时对隐藏文件处理不友好。一旦需求超出“一次性把几个文件从A搬到B”,就该看rsync和sftp了。

6.1 rsync通过SSH通道实现增量同步

rsync本身不依赖SSH,但它最常见的用法之一就是通过-e ssh跑在SSH隧道上,这样之前配置的免密钥认证可以完全复用。一个典型命令:

rsync -avz --partial --append-verify -e ssh ./data/ user@192.0.2.10:/data/

参数含义拆开看:-a启用归档模式,递归复制并保留权限、时间戳等属性;-v显示过程;-z传输压缩;--partial保留已传输的部分文件;--append-verify在续传时校验已接收的内容。最关键的是,rsync只在文件大小或校验不同时才会重新传输,大量未变化的小文件不会被重复搬运,这在同步几万个文件的目录时优势巨大。

如果远程SSH端口不是22,把-e写完整:

rsync -avz -e "ssh -p 2222 -i $HOME/.ssh/deploy_key" ./data/ user@192.0.2.10:/data/

6.2 rsync的删除同步

需要让远程目录和本地完全一致,包括删除本地已经不存在的文件时,rsync可以加--delete。这个场景下scp完全做不到,它只会单向覆盖或新增。不过--delete要谨慎使用,路径写错可能把远程文件清空,建议先加--dry-run试跑一遍,确认无误后再正式执行。

6.3 sftp用于交互式文件管理

如果记不清远程目录结构,又不想开额外的FTP服务,直接用sftp连上去最舒服:

sftp user@192.0.2.10

进入后可以ls、cd、mkdir,get远程文件拉到本地,put本地文件传到远程,体验就像在一个简化版的FTP客户端里操作。而且无需要额外服务配置,它就是走SSH通道。

6.4 选型参考

场景推荐工具原因
传一两个小文件,一次性scp命令短、依赖少
大批量小文件目录rsync增量同步,节省带宽
大文件中断后续传rsync--partial支持断点
让远程目录和本地保持一致rsync --delete支持删除多余文件
记不清结构,边看边传sftp交互式浏览目录
脚本自动推送部署包scp简单可靠,一行搞定

最后再分享一个小习惯。我很少直接scp -r搬运很大的源码目录,通常会先在源机器上tar czf打包,再传压缩包,到目标机器解压。虽然多了两步,但好处非常明显:一是传输字节数更少,二是文件属主和权限不会在复制过程中出现偏差,三是有了一份完整的发布包归档可以保留。如果你也经常在几台服务器之间搬运文件,不妨试试这个流程,然后再结合传文件的大小和频率,决定是继续用scp还是换成rsync。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询