远程管理 Linux 服务器这件事,第一次做和第一百次做完全是两种体验。第一次是照着教程敲一条 ssh 命令,连上了就欢天喜地;第一百次是半夜被告警叫醒,一边看着连不上的会话,一边判断到底是网络不通、密钥过期、还是自己白天改配置时手滑。远程管理 Linux 服务器的核心,从来不是“会不会敲 ssh”,而是你有没有把连通性、身份、会话、观测这四件事分层想清楚,出事时能不能按层往下剥。这篇文章我按实战顺序拆:先讲需求分层和方案选型,再讲打通第一次连接、把入口收到最小、长会话与文件流转、批量自动化,最后是我这些年踩过的坑和一张故障速查表。刚接触服务器的新手可以照着抄配置,手上管着十几台机器的运维也能从加固和排查两节里找到能直接用的东西。
1. 先把“远程管理”拆开看:需求分层与方案选型
1.1 从“能连上”到“敢长期挂着”的四层需求
很多人把远程管理理解成一条命令的事,实际上它至少分四层,每层解决的问题完全不同,混在一起想就会一直困在“为什么又连不上了”的循环里。第一层是连通性,解决的是数据包能不能从我的机器走到目标机器,这一层归网络、归防火墙、归服务监听地址管;第二层是身份与权限,解决的是“凭什么证明我是我”以及“我登录后能干什么”,归密钥、口令、用户组、sudo 策略管;第三层是会话与文件流转,解决的是长时间跑的任务断线怎么办、大文件怎么搬、内网端口怎么临时暴露出来调试;第四层是可观测与可回溯,解决的是出问题后能不能知道谁在什么时间做了什么。
这四层的排查顺序不能乱。我见过太多新手,连不上第一反应是去改 sshd 配置,结果折腾半小时发现是安全组没放行对应端口。正确的顺位永远是:先确认网络可达(ping、telnet 端口、nc 探测),再确认服务在听(ss -tunlp 看端口),再看认证日志(journalctl -u sshd、/var/log/auth.log),最后才动配置。把这条顺位刻进脑子里,能省掉大量的无效操作。
| 层级 | 典型问题表现 | 主要手段 | 排查入口 |
|---|---|---|---|
| 连通性 | 连接超时、Connection refused | 安全组、监听地址、路由 | ping、nc -vz、ss -tunlp |
| 身份与权限 | Permission denied、认证失败 | 密钥对、口令策略、sudo | ssh -v、auth.log |
| 会话与文件 | 断线任务丢失、传输中断 | tmux、rsync、端口转发 | 会话内现场、传输日志 |
| 可观测 | 不知道谁登录过、改了什么 | 登录日志、命令审计 | last、lastb、auditd |
1.2 为什么 SSH 是默认答案,什么情况才需要加东西
远程管理 Linux 有几种主流做法:SSH 命令行、图形化远程桌面、Web 管理面板、带外管理控制台。默认答案一定是 SSH,理由很实际:它从内核网卡一启动就能用,不依赖图形栈,几乎不占资源,而且能被脚本化和批量工具直接驱动。图形化远程桌面适合确实需要按鼠标操作的场景,比如跑某些带界面的运维工具,但它要额外装桌面环境,占用几百兆内存,网络抖动时体验极差。Web 面板上手最快,点几下就能装软件、看监控,但代价是它本身就是一个暴露在公网的高权限入口,一旦面板有漏洞或者口令弱,等于把门钥匙挂在门口。
选型上我的一般判断是这样:纯运维、跑服务、写脚本,SSH 就够;需要给不熟悉命令行的同事做只读查看,可以上轻量面板但必须限制在跳板机后;需要安装系统、改 BIOS、处理内核 panic 这种“系统已经起不来”的情况,只能靠带外控制台,那是云控制台里的 VNC 或者机房的 IPMI 类功能,属于另一条独立通道,跟 SSH 不在一个层面上,不能互相替代。
提示:不要在同一台机器上同时开 SSH、Web 面板、远程桌面三套入口。每多一个入口就多一份被扫描面,多一套凭据要维护,出事时还多一个不知道该查谁的方向。
1.3 端口与暴露面的取舍:改端口到底有没有用
关于“改掉默认 22 端口”这件事,网上说法两极分化。我的看法是:改端口是降噪,不是加锁。公网上的自动化扫描器基本是全端口扫的,你改成 2222 它们一样能找到,所以它挡不住有心人;但全端口扫描的成本比只扫 22 端口高一个数量级,你会少掉大量无脑爆破流量,日志会干净很多,看 auth.log 时不必在上万条失败记录里翻找真正值得关注的那几条。
真正决定安全性的不是端口号,而是“谁能从哪个地址打到这个端口”。我这里用三层收敛:第一,sshd 用ListenAddress只绑内网网卡或者指定地址,公网网卡干脆不监听;第二,云上安全组只放行固定出口 IP(x.x.x.x/32),不用0.0.0.0/0;第三,能走跳板机就全部走跳板机,业务机器只允许跳板机的地址进来。这三层里任何一层做扎实了,端口是不是 22 都没那么要命。
还有一点常被忽略:MaxStartups 10:30:100这个默认值。它的意思是未完成认证的连接最多 10 个,超过 10 个之后按 30% 概率随机丢弃新连接,100 个是硬上限。这个设计本意是抗暴力破解,但如果你用批量工具同时并发连几十台机器,或者某些网络环境下 TCP 握手完成了、认证阶段卡住,就会看到随机性的“连不上”。排查这类问题的思路不是查密钥,而是看这个参数和实际并发量是否匹配。
2. 打通第一次连接:链路拆解与免密登录
2.1 一次 ssh 命令背后的五个环节
敲下ssh user@host之后,发生了五件事,每一件失败的表现都不一样,认清楚这一点,排障效率会翻倍。第一步是名字解析,host 如果是域名要走 DNS,解析错了就会卡住几秒然后报“Could not resolve hostname”,这类问题的特征是等待时间明显偏长。第二步是TCP 三次握手,目标是 22 端口(或你改过的端口),这一步失败典型报错是“Connection timed out”(包被丢)或者“Connection refused”(包到了但没服务在听),这两个报错的含义完全相反,前者查网络和防火墙,后者查 sshd 有没有启动、有没有绑在你以为的地址上。
第三步是协议版本协商与密钥交换,双方交换算法列表,算出一组对称密钥。这一步失败通常报“no matching host key type”或“no matching key exchange method”,原因是客户端和服务端的算法集没有交集,多见于一端是比较老的系统。第四步是用户认证,走公钥、口令或者组合,失败报“Permission denied (publickey)”,注意括号里写的是什么,它告诉你服务端允许哪些认证方式,非常有用。第五步是会话通道建立,认证成功后分配 pty、启动 shell,这一步如果卡住,多半是登录脚本(.bashrc、/etc/profile)里有阻塞的操作。
一把梭的排查命令是ssh -vvv user@host,三个 v 会把上面每个环节的细节全打出来,包括客户端支持的算法、服务端回过来的算法、公钥尝试了哪几个、被拒绝了几个。我处理连接问题的第一动作永远是加 -vvv 看日志,而不是猜。
2.2 密钥对:生成参数怎么选
口令登录在公网服务器上基本可以当不存在,密钥登录是标配。生成命令我固定用这一条:
ssh-keygen -t ed25519 -a 100 -C "ops@laptop-2024" -f ~/.ssh/id_ed25519_ops几个参数说清楚。-t ed25519选的是椭曲线算法,密钥长度固定 256 位,安全强度大致相当于 RSA 3072 位,但密钥文件小得多、签名速度快得多、握手时传输的数据也少,对高延迟或弱网环境更友好。-a 100是给私钥的 passphrase 做密钥派生时的迭代轮数,默认是 16,调到 100 意味着每次用这个私钥都要多算一会儿,暴力猜测 passphrase 的成本相应提高,代价是解锁时多等零点几秒,完全值得。-C是注释,别写成“test”或者空着,写上“谁在哪台机器什么时候生成的”,将来清理旧密钥时你会感谢自己。-f指定文件名,一台设备一把密钥、按用途分开,是后面能安全轮换的前提。
RSA 不是不能用,但要注意两点:一是长度至少 3072,2048 在当下已经偏弱;二是较新版本的 OpenSSH 默认不再接受ssh-rsa(SHA-1 签名)这种老算法,如果你连的是很老的系统,可能需要显式打开,但那是妥协不是方案,正确做法是把老系统升级掉。下面这张表是我常用的选择依据:
| 算法 | 密钥长度 | 适用场景 | 备注 |
|---|---|---|---|
| ed25519 | 固定 256 位 | 绝大多数现代系统 | 首选,快且小 |
| rsa | 3072 或 4096 | 老系统兼容 | 别再生成 2048 |
| ecdsa | 256/384/521 | 已有体系依赖时 | 注意曲线选择 |
2.3 把公钥放上去的三种方式与权限位的坑
公钥放到服务端的~/.ssh/authorized_keys里。最省事的是ssh-copy-id -i ~/.ssh/id_ed25519_ops.pub user@host,它会自动处理目录创建和权限。手工方式就是cat追加,但必须同时把权限改对,否则 sshd 会静默拒绝公钥登录,日志里只留一句“Authentication refused: bad ownership or modes”,新手最容易卡在这里。
权限的硬性要求是:家目录不能被组或其他用户写(一般 755 或 750),~/.ssh必须是 700,authorized_keys必须是 600,私钥本地是 600。原因是 sshd 默认开了StrictModes yes,它会逐级检查从家目录到密钥文件的属主和权限,只要有一环是组可写,它就认为别人可能篡改过这个文件,直接放弃公钥认证。这个设计的出发点是好的,但报错信息非常不友好,所以记住这条经验:公钥登录莫名失败,先看权限位,再看日志。
第三种方式是首次开机自动注入,云主机创建时可以在控制台里预置公钥。这种做法很方便,但要注意两点:一是注入的公钥会进authorized_keys,如果你后面手工再改这个文件,可能把注入的覆盖掉导致自己进不去;二是不同镜像对注入的实现不一样,有的走 cloud-init,有的走镜像内置脚本,建议创建完先验证一下能不能登录,再动手做别的。
2.4 客户端 config:把一长串参数收起来
日常管理多台机器,靠记忆拼 ssh 参数不现实。在~/.ssh/config里写 Host 别名是效率提升最明显的一步:
Host web-prod HostName 10.0.3.21 User deploy Port 2222 IdentityFile ~/.ssh/id_ed25519_ops ServerAliveInterval 60 ServerAliveCountMax 3 TCPKeepAlive yes Host db-* User dba IdentityFile ~/.ssh/id_ed25519_dba ProxyJump bastionServerAliveInterval 60配合ServerAliveCountMax 3的含义是每 60 秒发一次保活探测,连续 3 次没响应就主动断开,总容忍时间 180 秒。这个值不是随便填的,它要跟中间网络设备的会话老化时间对上——很多 NAT 设备和负载均衡的空闲会话回收在 300 秒左右,如果保活间隔大于这个值,你会发现连接总是“莫名其妙就断了”,其实是中间设备先把你踢了。所以保活间隔要明显小于链路上最短的会话老化时间,60 到 120 秒是比较稳的区间。
ProxyJump是跳板机的正确用法,它让连接直接穿过跳板机建立端到端通道,不需要你在跳板机上开-L转发,也不需要把私钥复制到跳板机上。Host db-*这种通配写法能把一组机器的共同配置抽出来,避免每台都写一遍。
3. 加固:把暴露在公网的入口收到最小
3.1 sshd_config 关键参数逐条说明
改配置之前先备份,这是底线:cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak.$(date +%F)。下面这些参数是我在任何一台对外机器上都会过一遍的:
| 参数 | 建议值 | 作用与原因 |
|---|---|---|
| Port | 非默认端口 | 降低扫描噪声,不是安全边界 |
| ListenAddress | 指定内网地址 | 从根上不让公网网卡监听 |
| PermitRootLogin | no 或 prohibit-password | root 直接登录等于把最高权限账号暴露给爆破 |
| PasswordAuthentication | no | 关掉口令通道,只留密钥 |
| PubkeyAuthentication | yes | 保留密钥认证 |
| MaxAuthTries | 3 | 单连接最多试 3 次,减少爆破窗口 |
| LoginGraceTime | 30 | 认证阶段最多 30 秒,超时断开 |
| ClientAliveInterval | 60 | 保活探测间隔 |
| ClientAliveCountMax | 3 | 连续 3 次无响应断开 |
| AllowUsers | 白名单 | 只允许指定用户登录,缩小面 |
| X11Forwarding | no | 服务器不需要图形转发就别开 |
PermitRootLogin我不建议直接写no而不先确认有其他账号能 sudo,否则改完 reload 之后你还有当前会话可用,一旦断了就彻底进不去。稳妥做法是先建好普通账号、配好 sudo、验证密钥能登录、验证 sudo 能用,再关 root。
PasswordAuthentication no是收益最大的一条改动,但也是风险最高的一条,因为它会立刻把所有还在用口令登录的账号踢出去。上线前得先摸清楚有多少人、多少个自动化脚本还在用口令。我的做法是先在日志里统计一段时间,看有哪些 IP 或账号是走 password 认证成功的,把这些都迁到密钥之后再关。
3.2 认证之外再加一层:passphrase 与 ssh-agent
私钥文件落在磁盘上,就等于一把可以被复制的钥匙。给它加 passphrase,就等于钥匙上还挂了一道密码锁,文件被拷走也用不了。麻烦在于每次用都要输一遍,这时候靠ssh-agent解决:agent 在内存里缓存解密后的私钥,会话期间反复使用不再重复输入。
eval "$(ssh-agent -s)" ssh-add ~/.ssh/id_ed25519_ops ssh-add -lssh-add -l列出当前缓存的密钥指纹,用完记得ssh-add -D清空,尤其是共用设备上。这里有个必须说清楚的坑:ForwardAgent yes这个选项意味着你在远端机器上也能用本地的 agent 去认证其他机器,很方便,但前提是你信任那台远端机器上的所有有权限的用户——因为对方只要能在你登录期间访问那个 agent socket,就能拿你的身份去连别的主机。所以我的原则是:只在跳板机上开转发,且跳板机上只保留必要的运维账号,业务机器一律不开。
3.3 网络侧收敛:安全组、监听地址、fail2ban
安全组是云上第一道闸,规则要写成“默认拒绝入站,白名单放行”。运维入口只放自己固定出口的 IP,格式是x.x.x.x/32,别图省事写/0。如果出口 IP 会变(比如家里宽带),更稳的方案是全部走跳板机:跳板机放行少量固定 IP,业务机器只放行跳板机所在网段。这样即使你出差换了网络,只要跳板机能进,业务机器就不需要动。
fail2ban 解决的是“日志里那些一直在试的 IP”这件事。核心三个参数是maxretry、findtime、bantime。我一般设maxretry=5、findtime=600、bantime=3600,意思是 10 分钟内失败 5 次就封 1 小时。这个组合的依据是:正常人手输密码或密钥出错不会连续错 5 次,而自动化爆破每秒可能试几十次,5 次阈值足够把它们筛出来。如果开了bantime.increment,封禁时长会按次数递增,效果更好。
注意:fail2ban 封的是本机防火墙规则,如果服务前面还有一层负载均衡或反向代理,它看到的源 IP 可能全是那个代理的地址,一封就把所有人封了。这种情况要先确认 sshd 拿到的真实源地址是什么。
3.4 改完配置不断线的自检流程
改 sshd 配置最怕的就是改完 reload、当前会话一断、新连接进不来,只能去控制台救。我固定走这套流程,几年来没翻过车:
- 先确认有一个已经登录的会话保持不动,全程别关,这是你的逃生通道。
- 改完执行
sshd -t,它只做语法检查,不改变运行状态,报错就改到不报为止。 - 再执行
sshd -T | grep -E 'port|permitrootlogin|passwordauth',看实际生效的配置值,确认你改的那行没被其他 Include 文件覆盖。 - 新开一个终端,用新配置连接一次。连上了再往下走。
- 确认无误后
systemctl reload sshd(Debian 系服务名是 ssh,RHEL 系是 sshd),用 reload 而不是 restart,reload 不会踢掉现有连接。 - 再新开一个连接验证一次,两次都成功才算完。
如果是改端口,更稳的做法是先加一行ListenAddress 0.0.0.0:新端口,让新旧端口同时监听一段时间,确认新端口能连了,再删掉旧端口。配置里Port可以写多次,写两行就是同时监听两个端口,这个技巧在过渡期特别好用。
4. 长会话与文件传输:日常高频操作的稳做法
4.1 tmux:断线不丢工作现场
直接在 ssh 会话里跑长任务,是最常见的失误。网络一抖、笔记本合盖、家里路由器抽风,会话断了,正在跑的编译、数据导入、日志跟踪全部收到 SIGHUP,进程跟着没了。解决办法是在服务端套一层会话保持工具,tmux 是最常用的。
tmux new -s work # 新建名为 work 的会话 # 按 Ctrl-b 然后 d 脱离 tmux ls # 列出所有会话 tmux attach -t work # 重新接入它的原理不复杂:tmux 在服务端启动一个常驻进程作为“会话宿主”,你的 shell 跑在它下面,ssh 断掉只是断了客户端和宿主之间的那条连接,宿主和里面的进程完全不受影响,下次 attach 上去现场还在。这个思路跟“把任务丢到后台”不一样,nohup和&只能保住进程,看不到输出交互,遇到需要输入确认的脚本就没办法了。
几个我常用的操作:Ctrl-b c新建窗口,Ctrl-b n/p切窗口,Ctrl-b %左右分屏,Ctrl-b "上下分屏,Ctrl-b [进入滚动模式(用方向键或 PageUp 翻历史输出,按 q 退出)。滚动模式是新手最容易卡住的地方,因为 tmux 里鼠标滚轮默认不生效,得先进入这个模式。
4.2 传输工具选型:scp、rsync、sftp 怎么选
小文件偶尔传一次,scp最直接;大量文件、需要增量、需要断点续传,rsync是唯一正确答案;需要交互式浏览目录,sftp合适。三者的差别主要在于增量判断和失败恢复能力。
| 工具 | 增量传输 | 断点续传 | 典型场景 |
|---|---|---|---|
| scp | 不支持 | 不支持 | 单文件、临时拷贝 |
| rsync | 支持 | 支持 | 目录同步、大文件、定期备份 |
| sftp | 不支持 | 部分支持 | 交互式浏览与零散上传 |
我日常用得最多的是这条:
rsync -avzP --partial --append-verify \ -e "ssh -p 2222 -i ~/.ssh/id_ed25519_ops" \ ./data/ deploy@10.0.3.21:/srv/app/data/逐个解释:-a是归档模式,等于递归加保留权限、时间戳、软链接等属性,同步目录基本都用它;-v输出明细,-P等于--partial --progress,保留半截文件并显示进度;--append-verify是在续传时对已传部分做校验,比单纯--append安全,因为它会验证已有部分是否真的一致,避免源文件变了还硬接上去导致文件损坏。-e用来指定 ssh 的端口和密钥,很多人不知道 rsync 可以这样传参。
传输时间要提前估,别传了一半才发现要跑一整晚。算法很简单:数据量换算成 Mbit 除以带宽。比如 10 GB 数据,10 GB = 10240 MB = 81920 Mbit,走 20 Mbps 的理论下限是 81920 ÷ 20 = 4096 秒,约 68 分钟;实际算上协议开销、磁盘 IO、小文件随机读写,按 60% 到 70% 的效率折算,准备 100 分钟比较现实。如果文件特别碎(几万个几十 KB 的小文件),瓶颈往往不是带宽而是每次文件的元数据往返,这时候先打包成 tar 再传,速度能快好几倍。
4.3 端口转发:把远端内网端口搬到本地调试
有时候服务只监听在服务器的127.0.0.1,比如只允许本机访问的数据库、管理后台,你不想为了看一眼就把它改成0.0.0.0暴露出去。这时候用本地转发:
ssh -N -L 15432:127.0.0.1:5432 deploy@10.0.3.21含义是把本地的 15432 端口,通过这条 ssh 连接,转发到服务器视角的127.0.0.1:5432。-N表示不执行远程命令,只做转发。然后本地用数据库客户端连127.0.0.1:15432就等于连到了服务器的数据库,全程没有对外暴露任何端口。这个用法我认为是运维最实用的技巧之一,比改监听地址安全得多。
反过来的远程转发-R用于把本地服务临时映射到远端,比如你在本地起了个调试服务,想让服务器上的程序回调过来。默认情况下远端只监听127.0.0.1,要让其他机器访问需要在服务端配置GatewayPorts,这里要格外小心,因为它相当于在服务器上开了个面向外部的入口,用完立刻关掉。
4.4 传输中断、校验与断点续传
大文件传完不算完,得校验。最省事的做法是两端各算一次哈希对比:
sha256sum bigfile.tar.gz在源端和目标端分别执行,比对输出。如果文件特别大,用rsync -c或者--checksum会更省事,它会按块计算校验和来决定哪些块需要重传。
传输中断后不要急着重头来。rsync 保留的半截文件配合--append-verify能接着传,但要注意:如果源文件中途被修改过,续传可能拼出一个损坏的文件,所以续传完成后必须校验。我踩过最难受的一次坑是传一个 40 GB 的镜像,中途断了三次,第三次续传完成后没校验就用了,结果安装时报解压失败,回头重传才发现文件尾部是坏的。从那以后我的规矩是:超过 1 GB 的传输,完成后必算哈希。
5. 批量与自动化:从一台到几十台
5.1 Ansible 的无 agent 思路与最小 inventory
机器多了以后,一台台登录改配置不现实,也容易漏。批量工具里我偏好 Ansible,核心原因是它不需要在目标机器上装 agent,走的就是 SSH 通道,跟你手动登录是同一套凭据和同一套网络路径,不会引入新的暴露面和新的组件依赖。
最小可用配置就是一个 inventory 文件:
[web] 10.0.3.21 ansible_user=deploy ansible_port=2222 10.0.3.22 ansible_user=deploy ansible_port=2222 [db] 10.0.4.11 ansible_user=dba [all:vars] ansible_ssh_private_key_file=~/.ssh/id_ed25519_ops ansible_python_interpreter=/usr/bin/python3先跑一次连通性验证:
ansible -i inventory.ini all -m ping返回pong说明认证和 python 环境都没问题。这一步失败的话,问题基本都在 SSH 层,拿单机ssh -vvv的排查方法去查就行,不用怀疑 Ansible 本身。
5.2 两个能直接抄的 playbook 片段
第一个是批量更新并重启服务,带条件判断,避免无意义的重启:
- hosts: web become: true tasks: - name: 更新软件包缓存 apt: update_cache: true cache_valid_time: 3600 - name: 升级指定包 apt: name: nginx state: latest register: pkg_result - name: 仅在包发生变化时重载服务 service: name: nginx state: reloaded when: pkg_result.changed第二个是批量下发配置并校验语法,校验不通过就不生效,这样能避免一次性把所有机器改坏:
- hosts: web become: true tasks: - name: 下发 nginx 配置 template: src: templates/site.conf.j2 dest: /etc/nginx/conf.d/site.conf backup: true register: conf_result - name: 校验配置语法 command: nginx -t changed_when: false when: conf_result.changed - name: 校验通过后重载 service: name: nginx state: reloaded when: conf_result.changedbackup: true这个参数值得单独说:它会在覆盖前把原文件按时间戳备份一份,出问题时可以直接回滚,成本几乎为零,收益极大。changed_when: false是告诉 Ansible 这条命令只做检查不改状态,不然它每次都会报 changed,导致后面的 when 判断失效。
5.3 批量操作的灰度与幂等
批量操作最大的风险不是命令写错,而是一次性铺开到所有机器,错了就是全站故障。我的做法永远是灰度:先拿一台非核心机器跑,看结果;再拿一个小组跑,看监控;最后全量。Ansible 里可以用--limit控制范围:
ansible-playbook -i inventory.ini site.yml --limit 10.0.3.21 ansible-playbook -i inventory.ini site.yml --limit web --forks 5--forks控制并发数,默认是 5。机器多的时候调大能快,但要考虑目标机器和你自己这台跳板机的负载,以及 sshd 的MaxStartups限制,并发太高会触发前面说的随机丢连接。
幂等性是写 playbook 的基本功:同一个脚本跑一次和跑十次,结果应该一样。判断标准是看模块而不是看命令——用apt、service、template、copy这些模块天然幂等,用command、shell就要自己加creates、changed_when这些条件。我见过最常见的错误是用shell: echo "xxx" >> /etc/fstab这种写法,跑两次就重复追加两行,机器一重启就挂。
6. 观测、日志与出问题时的取证
6.1 谁在登录:把登录记录看明白
出问题时第一个问题往往是“是不是有人登进来了”。三个命令覆盖大部分需求:last看成功登录历史,读的是/var/log/wtmp;lastb看失败记录,读的是/var/log/btmp,这个文件有些系统默认不存在或者权限不对,需要先确认;journalctl -u sshd --since "1 hour ago"看服务端日志,能看到每次连接的源 IP、认证方式、结果。
last -n 20 lastb -n 20 journalctl -u sshd --since today | grep -E "Accepted|Failed"Accepted开头的行是成功认证,后面会写明用的什么方式(publickey 还是 password)以及密钥指纹;Failed是失败。如果你发现某个陌生 IP 连续Failed之后突然出现一条Accepted publickey,那就要警觉了,说明对方拿到了有效密钥,这时候要立刻查这台机器上的公钥列表、查这个时间段的命令历史。
6.2 登录之后的资源排查顺序
登进去发现机器很慢,别急着top一通乱看,按固定顺序过一遍效率最高:
uptime看负载和运行时长。把 1 分钟、5 分钟、15 分钟三个负载值跟nproc的核数比,超过核数就是排队了,而且看三个值的趋势能判断是突发还是持续。free -m看内存,重点看available这一列,不是free。free小但available大是正常的,说明缓存用得好。df -h看磁盘空间,再补一个df -i看 inode。磁盘还有空间但写不进去,八成是 inode 用完了,通常是某个目录堆了几百万个小文件。ss -tunlp看端口和连接,排查有没有异常连接数、有没有服务没起来。ps aux --sort=-%cpu | head和ps aux --sort=-%mem | head看谁在吃资源。dmesg -T | tail -50看内核有没有 OOM killer 记录,这一步经常能一句话定案——进程莫名其妙消失,多半是被内核杀了。
这套顺序的价值在于它从全局到局部,先判断是资源问题还是进程问题,再往下钻,不会一上来就陷进某个具体进程的细节里。
6.3 操作留痕:会话记录与命令历史
多人共用一台机器时,“谁改的”这个问题很难回答。两个低成本手段:一是给每个运维开独立账号,sudo 日志天然带用户名,/var/log/auth.log或journalctl -u sudo里能看到谁在什么时候执行了什么命令;二是在关键机器上开启会话录制,script命令就能做:
script -t 2> /var/log/sessions/user_$(date +%s).timing \ -a /var/log/sessions/user_$(date +%s).log它把整个终端会话的输入输出都记下来,配合.timing文件还能回放。这个做法有磁盘开销,也不适合所有场景,但对跳板机和核心数据库机器是值得的。
另外提醒一句:不要指望history能当审计。用户可以随便清、可以设HISTSIZE=0,history是给自己用的便利工具,不是审计手段。
7. 踩坑记录与速查表
7.1 典型故障速查表
下面这张表是我这些年实际遇到过的问题汇总,按“现象 → 大概率原因 → 第一步动作”组织,出问题时可以直接对着找:
| 现象 | 大概率原因 | 第一步动作 |
|---|---|---|
| Connection timed out | 安全组未放行、路由不通 | 从对端nc -vz探端口 |
| Connection refused | sshd 未启动或监听地址不对 | ss -tunlp | grep ssh |
| Permission denied (publickey) | 公钥未下发、权限位不对 | 看 auth.log 的拒绝原因 |
| 连接几秒后自动断 | 保活间隔大于中间设备老化时间 | 调小 ServerAliveInterval |
| 偶发连不上,重试就好 | MaxStartups 限流 | 看并发量与参数是否匹配 |
| 改配置后新连接进不来 | 语法错误或参数被覆盖 | sshd -t、sshd -T |
| 公钥登录忽然失效 | 家目录或 .ssh 权限被改 | 逐级检查 700/600 |
| rsync 续传后文件损坏 | 源文件中途变化 | 完成后算 sha256 对比 |
| 批量任务部分机器失败 | 认证方式不统一、python 路径不同 | 单机-vvv复现 |
7.2 几条不常见但很救命的经验
第一条,永远保留一个后门会话。改任何跟登录相关的配置之前,先开一个会话放着不动,改完用新会话验证,验证通过再关掉旧会话。这一条看起来笨,但它救过我至少三次。
第二条,别在服务器上直接编辑 sshd_config 后立刻 reload。正确顺序是改文件、sshd -t校验、开新会话验证、再 reload。reload 本身不踢人,但如果配置有语法错误,reload 会失败而旧进程继续跑,你可能以为改成功了,其实没有,下次重启才爆。
第三条,密钥要能轮换。很多人一把密钥用了好几年,所有机器都是同一把。正确做法是一台设备一把,服务端authorized_keys里带上清晰的注释,定期清理不再使用的。真要出事时,你能在十分钟内把某个人的密钥从所有机器上摘掉,而不是花一天去梳理哪把是哪把。
第四条,时间同步别忽略。密钥认证本身不依赖时间,但很多配套组件依赖,比如日志时间戳对不上会让排查变成猜谜,证书类的认证更是直接因为时钟偏差失败。timedatectl看一眼,确认同步服务是 active 的,这是性价比极高的一次检查。
第五条,别把运维入口和业务入口混在一起。业务端口对公网开放是正常的,运维入口对公网开放是能省则省的。这两件事分开想,很多架构上的取舍会清晰很多。
说点我个人的体会。远程管理这件事,新手阶段追求的是“能连上”,中期追求的是“连得稳、改得安全”,到了后期你会发现真正花时间的是“出事时能快速定位”。所以我现在的习惯是:每台新机器上线前,把密钥、监听地址、安全组、日志这四样一次性配到位,再写一份这台机器的连接说明丢进文档里,包括用哪个账号、哪个端口、走不走跳板、出问题先看哪个日志。这份东西写的时候花十分钟,半年后接手的人(很可能就是我自己)能省下几个小时。工具会换,命令会忘,但“分层想问题、改动前留退路、改完必验证”这三条,放在哪个年代都不过时。