1. 文件传输的两种身份验证路径
scp这个命令,做过运维或者后端开发的人都不陌生。它的全称是 secure copy,底层依赖 SSH 协议,用来在本地和远程主机之间、或者两台远程主机之间拷贝文件。很多人第一次接触它,是因为要把本地打好的包传到服务器上,或者从服务器上把日志拉下来分析。但真正让新手卡住的,往往不是scp本身的语法,而是“我到底该用哪种方式证明我是我”——也就是身份验证这一关。
标题里提到的“私钥或者账号”,其实点出了scp最核心的两条认证路径:一条是基于密码的账号认证,另一条是基于密钥对的私钥认证。这两条路都能走通,但走起来的体验、安全性、适用场景完全不一样。我见过太多人在这上面反复踩坑:有人明明配了密钥却还在被提示输密码,有人把私钥权限设成 777 导致连接直接被拒,还有人搞不清楚-i参数到底该指向哪个文件。
这篇文章就是要把这两种方式彻底讲透。不管你是刚接触 Linux 的新手,还是已经用了几年scp但一直没搞明白背后逻辑的老手,我都会从原理、操作、参数、排错几个维度把这件事拆开揉碎。你会看到具体的命令怎么写、每一步为什么这么写、遇到报错怎么查、以及我在实际工作中总结出来的一些“文档里不会写”的经验。全文围绕scp的私钥认证和账号认证展开,所有示例都可以直接拿去改改就用。
2. 先搞清楚 scp 到底在干什么
2.1 scp 与 SSH 的关系
很多人把scp当成一个独立的文件传输工具,其实它更像是 SSH 协议的一个“应用层封装”。当你执行scp local.txt user@host:/tmp/的时候,背后发生的事情是这样的:本地先启动一个 SSH 客户端进程,跟远程主机的 SSH 服务端建立一条加密通道,然后在这条通道里跑一个远程的scp进程来完成文件读写。也就是说,scp的认证过程完全复用了 SSH 的认证机制。
这个认知很重要,因为它意味着:你在 SSH 里能用的认证方式,在scp里基本都能用;你在 SSH 里遇到的认证问题,在scp里也会原样出现。很多人排查scp问题的时候只盯着scp命令本身,其实应该把思路拉到 SSH 层面去看。
提示:如果你能用
ssh user@host正常登录,那么scp大概率也能正常工作。反过来,如果ssh都连不上,先别折腾scp,把 SSH 连接问题解决掉再说。
2.2 两种认证方式的本质区别
账号认证,说白了就是“用户名 + 密码”。你告诉服务器你是谁,然后提供密码证明你确实是那个人。这种方式的好处是简单直接,不需要提前配置任何东西,只要有账号密码就能用。坏处也很明显:密码会在网络上传输(虽然是加密通道,但服务端拿到的是明文密码)、容易被暴力破解、没法做自动化(每次都要手动输)。
私钥认证,走的是非对称加密的路子。你本地生成一对密钥,一个叫私钥自己留着,一个叫公钥放到服务器上。连接的时候,服务器用公钥加密一段随机数据发给客户端,客户端用私钥解密后发回去,服务器验证成功就放行。整个过程私钥从不离开本地,安全性高得多,而且可以免密登录,特别适合脚本自动化。
| 对比维度 | 账号密码认证 | 私钥认证 |
|---|---|---|
| 配置复杂度 | 低,开箱即用 | 中,需要生成和分发密钥 |
| 安全性 | 较低,密码可能被暴力破解 | 较高,私钥不出本地 |
| 自动化友好度 | 差,需要交互输入 | 好,可完全免密 |
| 适用场景 | 临时连接、个人测试 | 生产环境、CI/CD、批量运维 |
| 密钥/密码管理 | 靠记忆或密码管理器 | 靠文件权限和 passphrase |
2.3 什么时候该用哪种方式
我的建议很直接:只要是长期使用的服务器,一律上私钥认证。账号密码只适合临时场景,比如你刚拿到一台测试机,还没来得及配密钥,先用密码登进去把公钥传上去。生产环境如果还在用密码认证,那基本等于把门虚掩着。
但私钥认证也不是没有代价。你需要管理私钥文件、设置正确的权限、处理 passphrase、在多人协作时分发公钥。这些工作在小规模场景下不算什么,但机器一多就需要借助配置管理工具来做了。不过对于个人开发者或者小团队来说,手动管理几十台机器的密钥也完全可行。
3. 账号密码方式:最直接但也最需要小心
3.1 基本命令写法
账号密码方式的scp命令非常直观。从本地传到远程:
scp /path/to/local/file.txt username@remote_host:/path/to/remote/directory/从远程拉到本地:
scp username@remote_host:/path/to/remote/file.txt /path/to/local/directory/执行后,终端会提示你输入username@remote_host的密码。输入正确后,文件就开始传输了。传输过程中会显示进度条,包括百分比、已传输大小、速度和剩余时间。
这里有几个细节值得注意。第一,远程路径如果以/开头,就是绝对路径;如果不以/开头,则是相对于该用户家目录的路径。第二,如果目标是一个目录,文件会被拷贝到该目录下并保持原文件名;如果目标是一个文件路径,文件会被重命名。第三,scp默认使用 SSH 的 22 端口,如果服务端改了端口,需要用-P(大写)指定。
scp -P 2222 local.txt user@host:/tmp/注意:
scp指定端口用的是大写-P,而ssh用的是小写-p。这个大小写差异坑过无数人,我也曾经因为写成小写-p而困惑了半天。
3.2 密码认证的交互细节
当你执行scp命令后,如果认证方式是密码,终端会弹出类似这样的提示:
user@host's password:这时候你输入的密码不会显示在屏幕上,连星号都没有。这是正常的安全设计,不是终端卡死了。输完直接回车即可。
但这里有个容易被忽略的问题:如果你在脚本里调用scp,它会一直卡在密码提示那里等待输入,导致脚本挂起。解决办法要么是改用私钥认证,要么是用sshpass这类工具把密码传进去。但sshpass本身有安全风险,因为密码会出现在命令历史或进程列表里,生产环境不建议用。
# 不推荐在生产环境使用 sshpass -p 'your_password' scp local.txt user@host:/tmp/如果非要在脚本里用密码认证,更好的做法是配置 SSH 的ControlMaster复用连接,先手动建立一次连接,后续的scp复用这个已认证的通道。不过这套配置比较复杂,后面讲私钥认证时会提到更优雅的方案。
3.3 账号密码方式的常见坑
第一个坑是用户名写错。很多人习惯用root,但很多服务器默认禁止root直接登录,这时候你会看到Permission denied。解决办法是先用普通用户登录,再用sudo提权,或者让管理员给你开一个专用账号。
第二个坑是密码里有特殊字符。比如密码里包含$、!、\这些字符时,在某些 shell 环境下会被解释成其他含义。虽然scp的密码输入是在 SSH 层面处理的,不经过本地 shell 解析,但如果你用sshpass传密码,就需要注意转义问题。
第三个坑是频繁输密码导致体验极差。如果你需要反复在本地和服务器之间传文件,每次都输密码会让人崩溃。这时候就该考虑私钥认证了。
4. 私钥认证:一次配置,长期省心
4.1 生成密钥对
私钥认证的第一步是在本地生成一对密钥。现在推荐用 Ed25519 算法,它比传统的 RSA 更短、更快、更安全。命令如下:
ssh-keygen -t ed25519 -C "your_email@example.com"执行后会提示你选择保存路径,默认是~/.ssh/id_ed25519。如果你有多台服务器需要不同的密钥,可以指定不同的文件名,比如~/.ssh/id_ed25519_work。接着会提示你输入 passphrase,这个相当于给私钥再加一层密码保护。如果你追求极致方便,可以留空;如果追求安全,建议设置一个强 passphrase,然后配合ssh-agent来避免每次输入。
如果你需要兼容一些老旧的服务器,可能还得生成 RSA 密钥:
ssh-keygen -t rsa -b 4096 -C "your_email@example.com"生成完成后,~/.ssh/目录下会出现两个文件:id_ed25519(私钥)和id_ed25519.pub(公钥)。私钥必须严格保密,公钥则可以随意分发。
4.2 把公钥放到服务器上
有了密钥对之后,需要把公钥内容追加到服务器的~/.ssh/authorized_keys文件里。最方便的方式是用ssh-copy-id:
ssh-copy-id -i ~/.ssh/id_ed25519.pub user@host这条命令会自动帮你创建~/.ssh目录、设置权限、把公钥追加到authorized_keys。如果服务器改了 SSH 端口,需要加-p参数:
ssh-copy-id -i ~/.ssh/id_ed25519.pub -p 2222 user@host如果ssh-copy-id不可用(比如在某些精简系统上),可以手动操作:
cat ~/.ssh/id_ed25519.pub | ssh user@host "mkdir -p ~/.ssh && chmod 700 ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys"这条命令看起来长,但逻辑很清晰:本地读取公钥内容,通过 SSH 传到远程,远程创建目录、设置权限、追加内容、再设置文件权限。
提示:
authorized_keys文件的权限必须是 600,.ssh目录必须是 700。如果权限不对,SSH 服务端会拒绝使用这个文件,你会看到Authentication refused: bad ownership or modes之类的错误。
4.3 用私钥执行 scp
公钥配置好之后,就可以用私钥来传文件了。如果私钥是默认路径和默认名称,scp会自动使用,不需要额外参数:
scp local.txt user@host:/tmp/如果私钥不在默认位置,或者你想指定某个特定私钥,用-i参数:
scp -i ~/.ssh/id_ed25519_work local.txt user@host:/tmp/如果私钥设置了 passphrase,每次使用时会提示你输入。为了避免重复输入,可以用ssh-agent把私钥加载到内存中:
eval "$(ssh-agent -s)" ssh-add ~/.ssh/id_ed25519_work这样在当前 shell 会话中,后续的scp和ssh都会自动使用已加载的私钥,不再提示输入 passphrase。
4.4 私钥认证的权限陷阱
私钥文件的权限是新手最容易踩的坑。SSH 对私钥权限的要求非常严格:必须是 600,也就是只有所有者可读写。如果你把私钥设成 644 甚至 777,SSH 会直接拒绝使用,并给出类似这样的警告:
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @ WARNING: UNPROTECTED PRIVATE KEY FILE! @ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ Permissions 0644 for '/home/user/.ssh/id_ed25519' are too open. It is required that your private key files are NOT accessible by others. This private key will be ignored.解决办法很简单:
chmod 600 ~/.ssh/id_ed25519这个限制背后的逻辑是:私钥是身份凭证,如果其他用户能读取,就相当于别人可以冒充你登录。所以 SSH 宁可拒绝使用,也不愿意冒这个风险。
5. 两种方式混用与进阶配置
5.1 同时指定私钥和密码
有时候你会遇到一种情况:私钥认证通过了,但私钥本身有 passphrase,而你又不想手动输入。这时候可以结合ssh-agent来解决,而不是在scp命令里同时传密码。scp本身没有“同时用私钥和密码”的参数,因为认证流程是 SSH 层控制的,它会先尝试公钥认证,失败后再回退到密码认证。
如果你想强制只用某一种方式,可以用-o参数指定:
# 只用公钥认证,禁用密码认证 scp -o PreferredAuthentications=publickey local.txt user@host:/tmp/ # 只用密码认证 scp -o PreferredAuthentications=password local.txt user@host:/tmp/这两个参数在排查认证问题时特别有用。比如你明明配了私钥但一直提示输密码,加上PreferredAuthentications=publickey后如果直接报错,说明公钥认证没生效,问题出在公钥配置或权限上。
5.2 用 SSH Config 简化命令
如果你经常连接多台服务器,每次都写一长串user@host和-i参数会很烦。这时候可以在~/.ssh/config里配置别名:
Host myserver HostName 192.168.1.100 User deploy Port 2222 IdentityFile ~/.ssh/id_ed25519_work IdentitiesOnly yes配置好之后,scp命令可以简化成:
scp local.txt myserver:/tmp/SSH 会自动读取配置文件里的主机名、用户、端口和私钥路径。IdentitiesOnly yes这个选项很重要,它告诉 SSH 只用配置里指定的私钥,不要尝试其他默认密钥。在多密钥环境下,这个选项能避免很多莫名其妙的认证失败。
5.3 批量传输与目录递归
scp默认只传单个文件。如果要传整个目录,需要加-r参数:
scp -r /local/directory user@host:/remote/directory/如果要传多个文件,可以在命令里列出多个源文件,最后一个参数是目标:
scp file1.txt file2.txt file3.txt user@host:/tmp/但scp不支持通配符在远程端的展开,比如scp user@host:/var/log/*.log .这种写法,通配符是由本地 shell 展开的,而本地 shell 无法知道远程有哪些文件。解决办法是用rsync替代,或者先用ssh列出文件再逐个传。
提示:传输大量小文件时,
scp的效率不如rsync,因为scp每个文件都要单独走一次协议交互。如果目录里有几千个小文件,建议用rsync -avz或者先打包再传。
6. 常见问题与排查技巧实录
6.1 认证失败问题速查表
| 报错信息 | 可能原因 | 排查方向 |
|---|---|---|
| Permission denied (publickey) | 公钥未正确配置 | 检查 authorized_keys 内容和权限 |
| Permission denied (password) | 密码错误或账号被锁 | 确认密码,检查账号状态 |
| Host key verification failed | 主机指纹变更 | 清理 known_hosts 中对应条目 |
| Connection refused | SSH 服务未启动或端口错误 | 检查服务状态和端口配置 |
| No such file or directory | 路径错误 | 确认远程路径存在且可写 |
| Permission denied (private key) | 私钥权限过开 | chmod 600 私钥文件 |
6.2 私钥不生效的排查思路
私钥不生效是最常见的问题,排查起来有一套固定流程。第一步,用ssh -v user@host看详细日志,-v会输出认证过程的每一步,你能看到 SSH 尝试了哪些密钥、哪些被拒绝、最终用了哪种方式。第二步,检查私钥权限是否为 600。第三步,检查authorized_keys文件权限是否为 600,.ssh目录是否为 700。第四步,确认公钥内容是否完整地追加到了authorized_keys,有时候复制粘贴会漏掉开头或结尾的字符。
如果以上都没问题,但私钥还是不生效,可能是 SSH 服务端的配置限制了认证方式。检查/etc/ssh/sshd_config里的PubkeyAuthentication是否为yes,以及AuthorizedKeysFile指向的路径是否正确。改完配置后记得重启 SSH 服务。
6.3 传输中断与续传
scp本身不支持断点续传。如果传输大文件时网络中断,只能重新传。这时候可以用rsync替代:
rsync -avz --partial --progress local_file user@host:/remote/path/--partial保留部分传输的文件,--progress显示进度。再次执行同样的命令时,rsync会从断点继续,而不是从头开始。
如果非要用scp,可以先用split把大文件切成小块,传完后再在远程合并。但这种方法比较笨,不如直接用rsync。
6.4 速度慢的优化方向
scp的速度受几个因素影响:网络带宽、加密算法、磁盘 I/O。如果带宽不是瓶颈,可以尝试换更快的加密算法:
scp -c aes128-gcm@openssh.com local_file user@host:/tmp/aes128-gcm比默认的chacha20-poly1305在某些 CPU 上更快,尤其是支持 AES-NI 指令集的处理器。另外,压缩选项-C对文本文件有效,但对已经压缩过的文件(如 zip、jpg)反而会增加 CPU 开销,不建议开。
还有一个容易被忽略的点:scp的进度条本身也会消耗一点性能。如果传大量小文件,可以加-q静默模式,减少输出开销。
7. 我个人在实际操作中的几点体会
用了这么多年scp,我最大的体会是:私钥认证一定要尽早配,不要等到被密码折磨得受不了才动手。我见过太多团队的生产服务器还在用密码登录,每次部署都要手动输密码,效率低不说,安全风险也大。花半个小时把密钥体系搭起来,后面几年都省心。
另一个体会是关于ssh-agent的。很多人觉得每次输 passphrase 很麻烦,干脆不设 passphrase。但私钥文件一旦泄露,没有 passphrase 就等于直接把钥匙给了别人。我的做法是设置一个强 passphrase,然后用ssh-agent加载,这样既安全又方便。在 macOS 上可以把ssh-add --apple-use-keychain加到启动脚本里,在 Linux 上可以用keychain工具管理。
最后分享一个小技巧:如果你需要在多台机器之间同步authorized_keys,可以把公钥内容统一放在一个版本控制的仓库里,然后用配置管理工具分发。这样新增或撤销密钥时,所有机器都能保持一致。手动管理几台机器还行,机器一多就很容易漏掉某台,留下安全隐患。