第 6 章 · SSH 与远程
一句话:用加密连接登录远程服务器、在上面跑命令、来回传文件,还能把服务器上的端口“拉”到本机来用。 用它还是用别的:登录 Linux 服务器、传部署文件、用 SSH 地址推拉 GitHub 都用它;想在 VS Code 里直接编辑服务器上的代码,用 Remote - SSH 扩展(第 2 章),它底下走的还是这一章的密钥和配置。 核对版本:OpenSSH 9.5(Windows 自带)、OpenSSH 9.6、tmux 3.4、rsync 3.2(Ubuntu 24.04),2026-10
6.1 安装与版本
Section titled “6.1 安装与版本”Windows 10(1809 起)和 Windows 11 自带 OpenSSH 客户端,在 PowerShell 里直接能用 ssh。以管理员身份打开 PowerShell 检查,State 是 Installed 就不用装:
Get-WindowsCapability -Online | Where-Object Name -like 'OpenSSH*'显示 NotPresent 时安装客户端:
Add-WindowsCapability -Online -Name OpenSSH.Client~~~~0.0.1.0macOS 自带 ssh、scp、sftp,不用装;tmux 要装:
brew install tmuxUbuntu 和 Debian。只连别人的服务器装第一行;要让这台机器能被别人 SSH 登录,再装 openssh-server:
sudo apt install openssh-client tmux rsyncsudo apt install openssh-serverWindows 本身没有 tmux 和 rsync,在 WSL 里用(第 4 章)。WSL 有自己的 ~/.ssh,和 %USERPROFILE%\.ssh 不是同一个目录:要在 WSL 里再生成一把密钥,或把 config 和私钥拷进去(cp 后 chmod 600),否则别名不认。
查版本:
ssh -V装了 Git for Windows 的机器上有两个 ssh:系统自带的在 C:\Windows\System32\OpenSSH\,Git Bash 里用的是 Git 自带的那个,版本号可能不同。两者读同一个 %USERPROFILE%\.ssh 目录,密钥和配置通用;WSL 里的 ssh 不读它,见上一段。
6.2 五分钟概念
Section titled “6.2 五分钟概念”- 密钥对(Key Pair):一对文件。私钥
id_ed25519留在本机,等于你的密码,不能给任何人;公钥id_ed25519.pub可以随便发,放到哪台服务器上,你就能免密码登录哪台。生成时可以给私钥加一个口令(passphrase),私钥文件被人拷走也打不开。 - authorized_keys:服务器上
~/.ssh/authorized_keys文件,一行一个公钥。登录时服务器出一道只属于这次连接的题,本机用私钥签名,服务器拿这个文件里的公钥验证签名,对得上就放行。私钥自始至终没有离开本机,见下图。 - known_hosts:本机的
~/.ssh/known_hosts,记录连过的每台服务器的指纹(fingerprint)。第一次连接时ssh问你是否信任,答yes就记下来;以后同一个地址的指纹变了,ssh拒绝连接,防止有人冒充服务器。 - config:本机的
~/.ssh/config,给主机起别名,把地址、用户名、端口、用哪把私钥写在一起。写好以后ssh myserver就够了,scp、rsync、git和 VS Code 的 Remote - SSH 都认这个别名;WSL 里另有一份,见 6.1。 - ssh-agent:后台程序,替你保管已经解锁的私钥。私钥有口令时,交给它解锁一次,之后每次连接都不用再输口令。
- 端口转发(Port Forwarding):借一条 SSH 连接搭隧道。本地转发把服务器上的端口拉到本机,比如服务器上只对本机开放的数据库;远程转发反过来,让服务器访问你电脑上的端口。
6.3 常用命令
Section titled “6.3 常用命令”<别名> 是 ~/.ssh/config 里 Host 后面的名字,写法见 6.5;没配别名时,所有 <别名> 都可以换成 <用户>@<主机>。表里的 ~/.ssh/… 在 PowerShell 里写成 $HOME\.ssh\…,PowerShell 7.6 之前交给 ssh-add 这类程序的是字面的 ~。tmux 的快捷键在 macOS 上也是 Ctrl,不是 Cmd,先按 Ctrl+B 松开,再按后面的键。
| 任务 | 命令 | 说明 |
|---|---|---|
| 密钥 | ||
| 生成密钥对 | ssh-keygen -t ed25519 -C "<邮箱>" |
回车存到默认位置,再设口令;-C 只是备注,方便认出是谁的 |
| 看公钥,复制这一行 | cat ~/.ssh/id_ed25519.pub |
一行 ssh-ed25519 AAAA… <邮箱>,要贴到服务器或 GitHub 的就是它 |
| 看指纹 | ssh-keygen -lf ~/.ssh/id_ed25519.pub |
和 GitHub 设置页显示的 SHA256:… 对照,确认是同一把 |
| 把公钥装到服务器 | ssh-copy-id <用户>@<主机> |
Linux、macOS、WSL 有;输一次服务器密码 |
| Windows 上装公钥 | Get-Content $HOME\.ssh\id_ed25519.pub | ssh <用户>@<主机> "mkdir -p ~/.ssh && chmod 700 ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys" |
Windows 没有 ssh-copy-id,这一行做同样的事,服务器是 Linux 时用 |
| 把私钥交给 agent | ssh-add ~/.ssh/id_ed25519 |
输一次口令,之后连接不再问;Windows 写 ssh-add $HOME\.ssh\id_ed25519,要先启动服务,见 6.6 |
| 看 agent 里有哪些私钥 | ssh-add -l |
报 Error connecting to agent 就是 agent 没在运行 |
| 连接 | ||
| 登录 | ssh <用户>@<主机> |
第一次会问是否信任指纹,核对后输 yes |
| 指定端口 | ssh -p 2222 <用户>@<主机> |
服务器的 SSH 不在 22 端口时 |
| 用别名登录 | ssh <别名> |
地址、端口、用户名全在 config 里 |
| 只跑一条命令 | ssh <别名> "df -h" |
输出显示在本地,跑完就断开 |
| 排查连不上 | ssh -v <别名> |
显示读了哪些配置、试了哪把私钥、卡在哪一步 |
| 看别名最终展开成什么 | ssh -G <别名> |
不连接,只列出生效的全部配置,查 config 写没写对 |
| 删掉某台主机的旧指纹 | ssh-keygen -R <主机> |
非 22 端口要写成 ssh-keygen -R "[<主机>]:2222" |
| 传文件 | ||
| 上传文件 | scp <本地文件> <别名>:<远程目录>/ |
远程路径不以 / 开头时,相对登录用户的家目录 |
| 下载文件到当前目录 | scp <别名>:<远程文件> . |
最后的 . 是本地当前目录 |
| 上传整个目录 | scp -r <本地目录> <别名>:<远程目录>/ |
不加 -r 会报错,见 6.6 |
| 增量同步目录 | rsync -avz --progress <本地目录>/ <别名>:<远程目录>/ |
只传有变化的文件,中断后重跑接着传;源结尾的 / 见 6.6;在 WSL 里用别名先看 6.1 |
| 让目标和源完全一致 | rsync -avz --delete <本地目录>/ <别名>:<远程目录>/ |
慎用:目标里源没有的文件全部删掉;先加 -n 试跑,看清删哪些 |
| 交互式传文件 | sftp <别名> |
进去后用 ls、cd、put <文件>、get <文件>,exit 退出 |
| 端口转发 | ||
| 本地转发 | ssh -N -L 5433:127.0.0.1:5432 <别名> |
本机 5433 接到服务器上的 127.0.0.1:5432;127.0.0.1 是从服务器那边看的地址 |
| 远程转发 | ssh -N -R 8080:127.0.0.1:3000 <别名> |
服务器上访问 127.0.0.1:8080,等于访问你本机的 3000 |
| SOCKS 代理 | ssh -N -D 1080 <别名> |
浏览器代理设成 SOCKS5 127.0.0.1:1080,流量从服务器出去 |
| 只转发、不开 Shell | 上面三条里的 -N |
终端停在那里不动是正常的,Ctrl+C 关掉隧道 |
| 经跳板机登录内网主机 | ssh -J <用户>@<跳板机> <用户>@<内网主机> |
跳板机和内网主机都要认你的公钥 |
| tmux(Linux、macOS、WSL) | ||
| 新建会话 | tmux new -s <名> |
在服务器上开,断线后会话里的程序照跑 |
| 离开会话 | Ctrl+B 后按 D |
会话留在后台继续运行 |
| 列出会话 | tmux ls |
每行一个会话名和窗口数 |
| 重新连上 | tmux attach -t <名> |
回到离开时的画面 |
| 左右拆分窗格 | Ctrl+B 后按 % |
上下拆分按 " |
| 在窗格之间切换 | Ctrl+B 后按方向键 |
当前窗格边框高亮 |
| 结束会话 | tmux kill-session -t <名> |
会话里还在跑的程序一起结束 |
| 服务端(Ubuntu) | ||
| 看 SSH 服务状态 | sudo systemctl status ssh |
Ubuntu、Debian 的服务名是 ssh,CentOS、RHEL 是 sshd |
| 检查配置有没有写错 | sudo sshd -t |
没有输出就是没错,有错会指出第几行 |
| 改配置后生效 | sudo systemctl restart ssh |
改了 Port 另要两步,见 6.5 |
6.4 典型工作流
Section titled “6.4 典型工作流”第一次配置免密登录服务器
Section titled “第一次配置免密登录服务器”服务器管理员给了你 IP、用户名和密码。做完以后用密钥登录,最后关掉密码登录:
- 生成密钥对,已经有
~/.ssh/id_ed25519就跳过:ssh-keygen -t ed25519 -C "<邮箱>"。 - 把公钥装到服务器,输一次服务器密码:
ssh-copy-id <用户>@<主机>。Windows 上用 6.3 表里“Windows 上装公钥”那一行。 - 测试:
ssh <用户>@<主机>,不再问服务器密码就成功了;设了私钥口令的,这里问的是口令。 - 在本机的
~/.ssh/config里写一个别名,内容见 6.5:code ~/.ssh/config。 - 用别名登录,这个窗口保持登录状态,后面出错靠它救回:
ssh <别名>。 - 在这个窗口里新建一个配置文件,写入
PasswordAuthentication no:sudo nano /etc/ssh/sshd_config.d/00-local.conf。 - 检查配置:
sudo sshd -t。 - 让配置生效:
sudo systemctl restart ssh。 - 另开一个本机终端,确认密钥还能登录:
ssh <别名>。 - 确认密码已经被拒绝,看到
Permission denied (publickey).就对了:ssh -o PubkeyAuthentication=no <别名>。
第 9 步失败时,回到第 5 步那个窗口删掉 00-local.conf,再 sudo systemctl restart ssh。
通过 SSH 隧道访问服务器上的数据库
Section titled “通过 SSH 隧道访问服务器上的数据库”服务器上的 PostgreSQL 只监听 127.0.0.1:5432,外网连不进去,这是对的。要在本机用图形客户端查数据,借 SSH 搭一条隧道。本地端口用 5433,是因为本机的 5432 可能已经被前言里用 Docker 起的 PostgreSQL 占了:
- 确认数据库在服务器上监听:
ssh <别名> "ss -ltn | grep 5432",地址是127.0.0.1:5432或0.0.0.0:5432都行。 - 打开隧道,这个终端会停住不动,别关它:
ssh -N -L 5433:127.0.0.1:5432 <别名>。 - 另开一个终端连本机 5433:
psql -h 127.0.0.1 -p 5433 -U <数据库用户>。 - 图形客户端(DBeaver、DataGrip 等)里主机填
127.0.0.1,端口填5433,用户和密码填服务器上数据库的。 - 用完回到第 2 步的终端,按
Ctrl+C关掉隧道。
经常要用,就在 config 的这个主机下加一行 LocalForward 5433 127.0.0.1:5432,以后 ssh -N <别名> 就自带这条隧道。
在服务器上跑长任务,断线也不怕
Section titled “在服务器上跑长任务,断线也不怕”直接在 SSH 里跑训练、迁移这类要几个小时的命令,网络一断、合上笔记本,命令就被结束了。放进 tmux 会话里跑:
- 登录服务器:
ssh <别名>。 - 新建一个会话:
tmux new -s job。 - 在会话里启动任务:
python train.py。 - 离开会话,任务继续跑:按
Ctrl+B后按D。 - 断开 SSH,也可以直接关掉终端:
exit。 - 过几个小时重新登录:
ssh <别名>。 - 看会话还在不在:
tmux ls。 - 回到会话,看到的是任务当前的输出:
tmux attach -t job。 - 任务跑完,结束会话:
tmux kill-session -t job。
用 SSH 地址克隆 GitHub 仓库
Section titled “用 SSH 地址克隆 GitHub 仓库”用 SSH 地址推拉 GitHub 不用每次输令牌(token):
- 生成密钥对,已有就跳过:
ssh-keygen -t ed25519 -C "<GitHub 邮箱>"。 - 把公钥复制到剪贴板。Windows 运行
Get-Content $HOME\.ssh\id_ed25519.pub | Set-Clipboard,macOS 运行pbcopy < ~/.ssh/id_ed25519.pub,Linux 运行cat ~/.ssh/id_ed25519.pub后手动复制那一行。 - 在 GitHub 网页右上角头像 → Settings → SSH and GPG keys → New SSH key,粘贴后保存。
- 测试,第一次会问是否信任 github.com 的指纹,输
yes:ssh -T [email protected]。看到Hi <用户名>! You've successfully authenticated, but GitHub does not provide shell access.就通了。 - 克隆,地址在仓库页面 Code 按钮的 SSH 标签下:
git clone [email protected]:<用户>/<仓库>.git。 - 已经用 HTTPS 地址克隆的仓库,改成 SSH 地址:
git remote set-url origin [email protected]:<用户>/<仓库>.git。
6.5 配置要点
Section titled “6.5 配置要点”配置文件的位置:
| 范围 | 路径 |
|---|---|
| 客户端配置(Linux、macOS) | ~/.ssh/config |
| 客户端配置(Windows) | %USERPROFILE%\.ssh\config |
| 已知主机指纹 | ~/.ssh/known_hosts,Windows 是 %USERPROFILE%\.ssh\known_hosts |
| 自己的密钥对 | ~/.ssh/id_ed25519(私钥)、~/.ssh/id_ed25519.pub(公钥) |
| 服务器上允许登录的公钥 | 服务器上登录用户的 ~/.ssh/authorized_keys |
| 服务端配置(Linux) | /etc/ssh/sshd_config,Ubuntu 还读 /etc/ssh/sshd_config.d/*.conf |
| 服务端配置(Windows) | %ProgramData%\ssh\sshd_config |
客户端值得写的项:
| 配置项 | 建议值 | 效果 |
|---|---|---|
Host |
一个好记的别名,如 myserver |
之后 ssh myserver 就能连 |
HostName |
服务器 IP 或域名 | 别名实际指向的地址 |
User |
登录用户名 | 不用每次写 <用户>@ |
Port |
服务器的 SSH 端口 | 默认 22,不是 22 时才写 |
IdentityFile |
~/.ssh/id_ed25519 |
有多把私钥时指定用哪把 |
ServerAliveInterval |
60 |
每 60 秒发一次心跳,空闲时连接不被防火墙掐断 |
ProxyJump |
跳板机的别名 | 等于每次加 -J,内网主机也能直接 ssh <别名> |
AddKeysToAgent |
yes |
第一次用私钥时自动交给 ssh-agent,之后不再问口令 |
同一项写了多次时,ssh 取第一次读到的值,所以具体主机写在前面,Host * 的通用设置放在文件最后。最小配置,写进 ~/.ssh/config:
# 别名,之后 ssh myserver 就能连Host myserver # 服务器地址 HostName 203.0.113.10 # 登录用户名 User deploy # SSH 端口,是 22 时可以不写 Port 2222 # 用哪把私钥 IdentityFile ~/.ssh/id_ed25519 # 经过下面的跳板机连过去,不需要就删掉这行 ProxyJump bastion
# 跳板机Host bastion HostName 203.0.113.5 User ops
# 对所有主机生效的设置放最后Host * # 每 60 秒发一次心跳,防止空闲断线 ServerAliveInterval 60 # 私钥第一次解锁后交给 ssh-agent AddKeysToAgent yes服务端值得改的三项。改之前务必保持一个已登录的会话不关,改完另开一个终端测试,确认能登录再关掉旧会话:
| 配置项 | 建议值 | 效果 |
|---|---|---|
PasswordAuthentication |
no |
只能用密钥登录,挡住全网扫描的密码爆破 |
PermitRootLogin |
prohibit-password |
root 只能用密钥登录;sshd -T 里显示成 without-password,是同一个意思 |
Port |
如 2222 |
日志里的扫描少很多;防火墙和云安全组要先放行新端口 |
服务端的配置也是第一次读到的值生效。Ubuntu 的 sshd_config 开头就读入 sshd_config.d/ 下的文件,云服务器镜像常在那里放一个写着 PasswordAuthentication yes 的 50-cloudimg-settings.conf,直接改 sshd_config 不起作用。所以自己的设置写进 /etc/ssh/sshd_config.d/00-local.conf,文件名以 00 开头排在最前:
# 只允许密钥登录PasswordAuthentication no# root 只能用密钥登录PermitRootLogin prohibit-password写完用 sudo sshd -T | grep -i passwordauthentication 看最终生效的值。Ubuntu 22.10 起 SSH 默认由 ssh.socket 监听端口,改了 Port 要运行 sudo systemctl daemon-reload 和 sudo systemctl restart ssh.socket,只重启 ssh 端口不会变。
6.6 注意事项
Section titled “6.6 注意事项”<用户>@<主机>: Permission denied (publickey).原因:服务器上没有你的公钥,或~/.ssh权限太宽被 sshd 忽略。处理:在服务器上chmod 700 ~/.ssh和chmod 600 ~/.ssh/authorized_keys;ssh -v看试了哪把私钥。WARNING: UNPROTECTED PRIVATE KEY FILE!,私钥被忽略。 原因:私钥文件别人也能读。处理:chmod 600 ~/.ssh/id_ed25519;Windows 上icacls $HOME\.ssh\id_ed25519 /inheritance:r /grant:r "$($env:USERNAME):(R)"。WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!,连接被拒绝。 原因:服务器重装过、换了 IP 上的机器,或真有人冒充。处理:向管理员核实指纹后,ssh-keygen -R <主机>删掉旧记录再连。- 闲置几分钟后卡死,最后报
client_loop: send disconnect: Broken pipe。 原因:中间的路由或防火墙掐掉了没有流量的连接。处理:在~/.ssh/config的Host *下写ServerAliveInterval 60。 - 改完 sshd 配置重启后,
ssh: connect to host <主机> port 22: Connection refused或一直超时。 原因:配置写错或新端口没放行,把自己锁在了外面。处理:改前保持一个已登录的会话,先sudo sshd -t;已锁死只能用云控制台的网页终端登录修复。 scp拷目录报scp: local "<目录>" is not a regular file,什么都没拷过去。 原因:没加-r,scp默认只拷文件。处理:scp -r <本地目录> <别名>:<远程目录>/;下载目录同样要加-r。rsync同步后远程多套了一层目录,成了<远程目录>/src/。 原因:源写成src是把目录本身拷过去,写成src/才是只拷里面的内容。处理:想让两边内容一致,源一律以/结尾,如rsync -avz src/ <别名>:<远程目录>/。- Windows 上
ssh-add报Error connecting to agent: No such file or directory。 原因:该服务默认禁用。处理:以管理员身份运行Get-Service ssh-agent | Set-Service -StartupType Automatic; Start-Service ssh-agent。 - Windows 上 agent 已加好私钥,
git push仍然每次问口令。 原因:Git 用的是自带的ssh,不找 Windows 的 ssh-agent。处理:git config --global core.sshCommand C:/Windows/System32/OpenSSH/ssh.exe。
6.7 深入阅读
Section titled “6.7 深入阅读”- OpenSSH 手册:ssh、scp、sftp、ssh-keygen、sshd 等每个程序的官方手册页入口。
- ssh_config 手册:~/.ssh/config 里每个配置项的准确含义和默认值。
- Windows 上的 OpenSSH:在 Windows 上安装 OpenSSH 客户端和服务器、启动服务、放行防火墙。
- GitHub:使用 SSH 连接:生成密钥、添加到 GitHub 账号、测试连接和排错,有中文。
- tmux wiki:tmux 官方入门、快捷键和配置说明。