1. 为什么需要配置SSH免密连接
先说一个场景:你每天要登录十几次甚至几十次服务器,每次都要输一遍密码。密码短的怕被爆,密码长的输到手酸,如果恰好密码里还带几个特殊字符,在终端里输错两次就得重新来。更别提批量操作的时候,脚本跑到一半卡在密码输入上,整个人就守在屏幕前等着输密码,效率低得一塌糊涂。
SSH免密连接解决的就是这个问题。配置完成之后,你从自己电脑连服务器、从服务器连服务器、在脚本里执行远程命令,全都直接通过,不再需要任何交互输入。很多人一听“免密”两个字就觉得是牺牲安全性换便利,其实恰恰相反——SSH免密用的是密钥对认证,安全性比密码认证高出一个量级。密码可以被爆破、被钓鱼、被中间人截获,而私钥文件本身不出现在网络上,服务器上存的只是一把公钥,别人就算拿到了服务器的全部文件,也无法反推出你的私钥。
这个技术适合谁?只要你跟Linux服务器打交道,无论是开发者、运维、学生还是折腾NAS和软路由的玩家,都值得配一套。尤其是这几类人受益最大:
- 需要频繁登录云服务器或公司跳板机的开发者
- 用VSCode Remote SSH、MobaXterm、Xshell等工具做远程开发的
- 写自动化脚本、Ansible、CI/CD流水线,需要批量执行远程命令的运维
- 不想在每台机器上都记住一堆不同密码的多机党
我自己从第一次配好免密到今天,已经攒了快十年的经验,中间踩过的坑不算少。这篇文章不写教科书式的废话,直接把原理讲透、把每一步的操作和参数拎出来,再把最常见的报错和排查方法一次说清楚。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 免密连接背后的原理:密钥对如何取代密码
2.1 非对称加密:公钥和私钥的分工
SSH免密连接的底层是非对称加密。所谓非对称,就是有一对密钥:一把公钥(public key),一把私钥(private key)。这两把钥匙之间的关系可以类比成一个上了锁的保险箱和一个只有你能打开的锁芯——公钥就是那把锁,你可以把锁随便发出去,让别人用它来锁东西;私钥就是那个锁芯,全世界只有你手里这一份,所有被公钥锁住的东西,只有私钥能打开。
在SSH免密场景里,你的电脑上生成一对密钥,然后把公钥上传到服务器的 ~/.ssh/authorized_keys 文件里。之后每次连接,服务器会验证“你确实持有那把对应的私钥”。具体过程是:
- 客户端发起SSH连接请求,声明自己要用哪个密钥
- 服务器检查公钥是否在
authorized_keys中,如果不在,直接拒绝 - 如果在,服务器生成一串随机数,用你的公钥加密后发给客户端
- 客户端用私钥解密,把解密结果返回给服务器
- 服务器核对结果,确认无误后放行
整个过程,私钥始终没有离开过你的电脑。即使有人在网络上抓包,抓到的也只是加密后的数据,在没有私钥的情况下无法解密。
2.2 为什么密钥比密码更安全
有人可能觉得:“密码简单但也没出过事啊,搞这么麻烦干嘛?”我举两个真实例子。
第一,密码爆破。网上有大量扫描工具在自动扫描公网服务器的SSH端口,拿到端口就开始用字典尝试登录。如果你用的是弱密码,比如 123456、admin、password,被爆破只是时间问题。即使密码不是特别弱,只要在线跑得够久,概率依然存在。而密钥认证下,暴力枚举私钥在当前的计算能力下实际上不可行——主流密钥长度是3072位或更高,枚举空间大到没有现实意义。
第二,密码的传递路径。密码是“知识”,必须输入到客户端才能用,所以密码天然会被传输、被记录。而私钥是“持有物”,不参与传输,服务器端连验证都无法验证出私钥内容。两种机制的安全模型完全不同。
2.3 免密的两种常见实现方式
在具体配置之前,有一个概念需要澄清:SSH免密有时候是指“密钥认证”,有时候是指“密钥认证+agent缓存”,这两个说法在实操中产生的效果不一样。
- 纯密钥认证:服务器端配置好公钥后,客户端用私钥登录。如果私钥本身没有设置密码短语(passphrase),那确实输入一次命令就直接进去了,全程无提示。
- 密钥认证 + ssh-agent缓存:私钥可以设置密码短语,首次连接时输入一次密码短语解锁私钥,之后把解锁状态存进ssh-agent内存,往后一段时间内(甚至一直)都不再询问。
绝大部分“配完还是要输密码”的坑,都出在第二点上。后面我会单独拿出一节讲清楚agent的用法。
3. 首次配置SSH免密的完整实操
3.1 生成密钥对:ssh-keygen 的参数怎么选
大多数Linux发行版和macOS都自带OpenSSH客户端,Windows 10以上也自带OpenSSH,所以一般情况下不需要额外装工具。打开终端,先确认一下有没有现成的密钥:
bash复制ls -la ~/.ssh/
如果里面已经有 id_rsa、id_ed25519 之类的文件,说明你之前生成过密钥,可以直接复用。如果还没生成过,或者想为某台新设备单独生成一对,执行:
bash复制ssh-keygen -t ed25519 -C "my-laptop-key"
这里解释一下参数选择:
-t ed25519:指定密钥类型。现在新密钥我默认首选Ed25519,它的安全性高、性能好、密钥文件短(公钥大概就68个字符),而且天然防护某些侧信道攻击。而老牌的RSA虽然兼容性更好,但为了同等安全性需要3072位或4096位,文件又长又慢。你的服务器SSH版本太老的话,有些只支持RSA,那就用ssh-keygen -t rsa -b 4096。-C "my-laptop-key":加一个备注。这个备注会写进公钥文件末尾,方便你日后在服务器上看到这条公钥时知道是哪台电脑用的。不加也可以,默认会取当前用户名和主机名,比如user@myhost。
执行后终端会问你要把文件保存到哪里,默认位置是 ~/.ssh/id_ed25519,直接回车。接下来会问是否设置密码短语:
bash复制Enter passphrase (empty for no passphrase):
这一步很关键。我的建议是:不设置密码短语,直接回车留空。 原因后面细讲,如果你有安全洁癖非要加,就等着在自动化脚本里被反复折磨吧。
生成完成之后,~/.ssh/ 目录下会多出两个文件:
| 文件 | 角色 | 能不能给别人 |
|---|---|---|
id_ed25519 |
私钥 | 绝对不能 |
id_ed25519.pub |
公钥 | 可以随意分发 |
3.2 把公钥上传到服务器
这一步有两条路:一条是有现成工具的自动做法,一条是纯手动的做法。
自动做法(推荐):使用 ssh-copy-id 这个脚本,几乎所有主流发行版都自带:
bash复制ssh-copy-id -i ~/.ssh/id_ed25519.pub user@server-ip
执行后它会推送到服务器的 ~/.ssh/authorized_keys 文件里,同时自动处理好目录权限问题。第一次连接时需要输入一次服务器密码,之后就不再需要了。
如果你的本机没有 ssh-copy-id(比如某些精简版Windows环境),用手动方式也一样:先把公钥内容复制到剪贴板:
bash复制cat ~/.ssh/id_ed25519.pub
然后登录服务器,把内容追加到 authorized_keys 文件末尾:
bash复制mkdir -p ~/.ssh
chmod 700 ~/.ssh
echo "粘贴你的公钥内容" >> ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys
这里每一步都有它的意义:mkdir -p 确保目录存在;chmod 700 让 .ssh 目录只有自己能进入;echo >> 是追加而不是覆盖(防止你之前已经有其他公钥被覆盖掉);最后 chmod 600 保证 authorized_keys 只有自己能读写。如果权限设置得太宽松,SSH服务端会直接拒绝使用这个文件,这是新人最容易踩的坑。
3.3 验证连接是否成功
上传公钥之后,最重要的一步是测试:
bash复制ssh user@server-ip
如果一切正常,你会发现这次连密码都不用输,直接就进到服务器了。如果想顺便看看用了哪个密钥、走了什么认证方式,可以加 -v 参数:
bash复制ssh -v user@server-ip
输出中会出现一行类似:
bash复制debug1: Offering public key: /home/you/.ssh/id_ed25519 ED25519 SHA256:xxxxxx
debug1: Server accepts key: /home/you/.ssh/id_ed25519 ED25519 SHA256:xxxxxx
看到 Server accepts key 就说明免密已经生效。如果还是提示要密码,或者报 Permission denied (publickey),参考后面的排查章节。
3.4 多台电脑要不要分别生成密钥
如果你家里一台电脑、公司一台电脑、还有一台笔记本,都有连服务器需求。我见过有人图省事,把一台机器上的私钥直接复制到另一台机器上,这种做法真的不推荐。
私钥复制多了会带来两个问题:一是私钥的暴露面变大,任何一台机器被攻破,等于所有机器的认证凭证都泄漏;二是你将来想吊销某台设备的访问权时,根本不知道撤哪一台。正确做法是每台设备各自生成一对密钥,把各自公钥都加到服务器的 authorized_keys 里。将来哪台不需要了,在服务器上删掉对应的那行公钥就行。
4. 进阶场景:多主机、密钥密码与常见工具搭配
4.1 用 SSH config 管理多台服务器
如果你手里的服务器一多,每次敲 ssh user@ip -p 端口 就显得很蠢。我们可以用 ~/.ssh/config 文件给每台服务器起一个简短别名,顺带把密钥、用户、端口都绑定好。
示例配置:
text复制Host my-vps
HostName 192.168.1.100
User root
Port 2222
IdentityFile ~/.ssh/id_ed25519
保存后,直接 ssh my-vps 就能连上,不需要记IP、用户名和端口号。前端开发朋友常用的VSCode Remote SSH,其实也读这套config,配完之后在VSCode里选 my-vps 就能直接连上,不用再填那堆参数。
还有个小技巧:如果你有多个相同配置的主机,可以用通配符一次匹配多个,比如:
text复制Host *.internal
User deploy
IdentityFile ~/.ssh/id_ed25519_internal
这样连 app1.internal、app2.internal 时,都会自动使用 deploy 用户和指定密钥。
4.2 密码短语(passphrase)与 ssh-agent 的正确用法
前面说生成密钥时不设置密码短语。那如果已经设置了怎么办?或者说我确实想给私钥加一道本地保护,能不能不牺牲免密的便利?
可以的,方案就是 ssh-agent。
先启动agent并加载密钥:
bash复制eval "$(ssh-agent -s)"
ssh-add ~/.ssh/id_ed25519
执行 ssh-add 时会要求输入一次密码短语,此后agent会在内存里保存解密后的私钥,再连接服务器就不再询问了。macOS 上还可以加上:
bash复制ssh-add --apple-use-keychain ~/.ssh/id_ed25519
把解锁状态存进系统钥匙串,重启后也不用重新添加。Linux桌面环境可以用 gnome-keyring 或 kwallet 做类似的事。
不过我个人建议是:个人电脑上,私钥就留空密码短语。 原因很简单——一旦设置了密码短语,你在没有agent的终端环境里(比如容器里、远程跳板机上)执行SSH操作时,每次都会卡在密码输入上,自动化脚本直接跑崩。如果你的电脑有全盘加密(比如macOS的FileVault、Windows的BitLocker、Linux的LUKS),私钥文件本身就已经被磁盘加密保护了,再加一道密码短语实际收益有限,但带来的麻烦非常具体。
4.3 Windows / VSCode / MobaXterm 场景的免密配置
Windows 10以上自带OpenSSH,很多场景下可以直接在PowerShell里用。几个容易踩的点:
权限问题:Windows下 ~/.ssh 的路径一般是 C:\Users\你的用户名\.ssh。如果之前用过Git自带的SSH,可能配置文件路径不一致,导致认不到私钥。最简单的做法是统一用Windows自带的 ssh 命令来生成和连接,并且把密钥放在 C:\Users\用户名\.ssh 下。如果你在GitBash里操作,路径也可能不一样(比如 /c/Users/用户名/.ssh),注意别搞混了。
VSCode Remote SSH:VSCode读取的是用户目录下的 ~/.ssh/config,所以只要你在本机终端能免密连上,VSCode里基本也能直接用。如果VSCode连不上但终端能连上,多半是VSCode用了内置的SSH客户端,且没有正确加载你的私钥或者私钥权限不对。Windows下右键私钥文件 → 属性 → 安全 → 高级,确保只有当前用户有权限,否则OpenSSH会拒绝使用。
MobaXterm:它自带了一套SSH客户端,配置界面里你会看到“Use SSH key”之类的选项,直接把私钥文件路径填进去就行。需要注意,MobaXterm的会话设置和全局设置都可能影响密钥读取,优先在会话设置里指定。
4.4 Docker容器内免密与批量登录的特殊场景
Docker容器里的免密问题,有相当多朋友问过。最常见的场景是:你在宿主机上能免密登录某台服务器,但进入容器之后却提示要密码。原因是容器内部可能没装OpenSSH客户端,或者容器里生成的 ~/.ssh 是全新的,里面既没有你的私钥也没有config配置。
解决方案是挂载或者复制密钥到容器里。开发调试用的话,直接挂载目录最省事:
bash复制docker run -it -v ~/.ssh:/root/.ssh ubuntu bash
生产环境肯定不建议这么干,密钥泄漏风险太大。更好的做法是容器内安装好SSH客户端,然后在构建镜像时把公钥写进镜像里的 ~/.ssh/authorized_keys(这只解决别人访问容器的问题),或者通过环境变量注入私钥(需要注意安全)。总之记住一点:容器内免密要先确认容器里有没有SSH客户端,再确认有没有可用的私钥,二者缺一不可。
批量登录的场景也提一下。比如你写个shell脚本,想循环连十台服务器执行同一命令:
bash复制for host in 192.168.1.101 192.168.1.102 192.168.1.103; do
ssh root@$host "uptime"
done
只要这些服务器上都放好了你的公钥,脚本就可以全程无交互跑完。如果中途某一台没配好,脚本会在那台卡住要密码。所以批量前一定要先把所有机器的公钥都配齐,用 ssh-keyscan 收集所有目标机器的 known_hosts 指纹提前写入,避免被交互确认打断。
5. 常见报错排查:免密失败的典型原因与解决办法
5.1 权限报错
报错样本:
bash复制Permissions 0777 for '/home/you/.ssh/id_ed25519' are too open.
OpenSSH对密钥文件的权限要求非常严格,私钥文件不能是其他用户可读的。解决办法:
bash复制chmod 600 ~/.ssh/id_ed25519
chmod 700 ~/.ssh
服务器端的 authorized_keys 也同理,权限必须是 600 或更严格。
5.2 连接超时或端口不通
报错样本:
bash复制ssh: connect to host 192.168.1.100 port 22: Connection timed out
这说明网络层面到不了这台机器的22端口。排查顺序是:
- 先ping一下,确认主机在线。
- 再确认目标端口通不通:Windows上
telnet 192.168.1.100 22,Linux或macOS上nc -vz 192.168.1.100 22。 - 如果端口不通,看云厂商安全组是否放行了22端口,或者本地防火墙是否挡了。
- 如果改了非默认端口,记得连接时用
-p 端口号,或者写进config。
5.3 Permission denied (publickey) 的排查思路
报错样本:
bash复制user@192.168.1.100: Permission denied (publickey).
这个报错属于“认证方式全部失败”,但并不是说服务器端禁止了密钥认证。可能原因很多,按概率从高到低排:
- 公钥没有正确写入
authorized_keys。最常见。检查服务器端文件内容确认公钥是否完整,以及是否有多余换行或空格。 - 客户端提交错误密钥。执行
ssh -v看实际发送了哪个公钥。如果发的是A公钥,服务器上存的却是B公钥,自然会被拒。 - 服务器端配置文件禁止了密钥认证。在
/etc/ssh/sshd_config里确认PubkeyAuthentication yes。 - AUTHORIZED_KEYS_FILE被改过。有些服务器把公钥文件路径改到了别处,比如
/etc/ssh/authorized_keys/用户这种结构。 - 服务器SELinux或AppArmor干扰。如果是装了图形面板的云服务器,有时面板会自动修改SSH配置,需要到面板的SSH设置里检查。
5.4 known_hosts 报错与指纹冲突
报错样本:
bash复制WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!
这是服务器换了系统或重新安装后,主机公钥变了,导致本机缓存的分身不一致。解决办法是删除旧的分身记录:
bash复制ssh-keygen -R 192.168.1.100
之后再连接会重新确认指纹。注意这个报错不要无脑直接忽略,如果服务器不是你主动重置过的,要警惕中间人攻击的可能。
5.5 PasswordAuthentication 被关后连不上
有时候你自己在服务器上把 PasswordAuthentication no 关掉了,然后突然发现本机免密还没配好,结果就彻底连不上了。这种“自己断自己后路”的坑,我也踩过一次。补救办法是通过云厂商的网页终端(VNC/IPMI)登录服务器,改回配置或重新配好公钥。这也是为什么我建议新服务器先配免密,再决定是否关闭密码认证,顺序别搞反。
5.6 中文用户名导致 .ssh 目录创建失败
有些Windows用户的用户名是中文,在GitBash里偶尔会看到类似:
bash复制Could not create directory '/c/users/账户名/.ssh'
这是因为SSH客户端对中文路径支持不完善。解决办法是显式指定密钥文件路径,比如:
bash复制ssh -i "C:/Users/中文用户名/.ssh/id_ed25519" user@server-ip
或者把密钥放到一个纯英文路径下,比如 C:/keys/id_ed25519,配合config文件指定路径。
5.7 常见问题速查表
| 症状 | 可能原因 | 首选排查动作 |
|---|---|---|
| 连接超时 | 网络不通/端口被挡 | nc -vz 主机IP 22 |
| 要输入密码 | 公钥未上传/密钥不匹配 | ssh -v 看使用的密钥 |
| Permission denied | 公钥权限/目录权限过宽 | 检查 server ~/.ssh 权限 |
| REMOTE HOST IDENTIFICATION HAS CHANGED | 服务器重装/密钥更新 | ssh-keygen -R 主机 |
| 断连频繁 | 网络不稳/服务器心跳超时 | 修改 ServerAliveInterval |
| 脚本卡在密码输入 | agent未加载/私钥有密码 | ssh-add -l 检查 |
6. 从入门到理顺:我的个人经验与建议
做了这么多年运维和开发,SSH免密一直是我在新机器上最先配置的内容。有几个经验供参考。
第一,密钥类型优先选Ed25519,别再用旧默认的RSA 2048。除非你确实要连老版本设备(某些旧交换机的SSH实现只认RSA),否则Ed25519都是更好的选择。生成时顺手带个 -C 备注,以后翻服务器上几十条公钥时不会一头雾水。
第二,每台电脑一套密钥,不要复制私钥。私钥复制得越多,对你的整体安全性伤害越大。丢了私钥也没什么好办法,只能在服务器上一行行删公钥。
第三,先确保免密成功,再考虑关闭密码认证。改 /etc/ssh/sshd_config 之前,先开两个会话,一个用来改配置,另一个保持登录状态备用。改错了还能在备用会话里补救,不会把自己锁在门外。
第四,一旦发现服务器被陌生IP大规模爆破,赶紧上fail2ban并考虑直接关闭密码认证。现在公网上的SSH端口满世界都是扫描器,我见过一台裸机刚开机几小时,auth日志里就躺了几千条失败尝试。这种情况下如果你还在用弱密码,就是在裸奔了。免密+禁用密码认证,才是真正安稳的状态。
最后再分享一个小习惯:我习惯在 ~/.ssh/config 里给不同用途的主机分组,加上注释,比如“生产环境”“测试环境”“客户内网”,并刻意把生产环境的密钥和生产环境的 Host 绑定在一起。这样每次操作时心里都有数,不容易连错机器。配置文件的 IdentityFile 指向不同密钥,也能避免某台机器误用了生产密钥的尴尬。
SSH免密配置本身并不难,难点在于理解原理、知道报错之后往哪里查。希望这篇文章能帮你少走一些弯路。
