如果你手里有一台公网服务器,别急着高兴,第一件要正视的事就是:从开机那一刻起,它就已经被默默盯上了。我几年前第一次租用云主机,熬夜部署好环境,第二天顺手翻了一下登录日志,发现凌晨三点左右,一个陌生IP正锲而不舍地用各种弱密码尝试root登录。这让我第一次对“SSH(Secure Shell)这个看似稀松平常的远程管理协议”产生了敬畏:它不仅是开发者和运维登录Linux服务器的标准通道,也是攻击者每天扫描、爆破、试探的头号目标。
这篇文章不是搬运文档,而是我这些年实际维护多台服务器、反复踩坑后的一份SSH服务实战梳理。我会从一次真实的爆破观察开始,讲到sshd_config各项配置的作用、密钥认证怎么一次到位、怎样用fail2ban和登录限制做加固、遇到各种“连不上”该按什么思路排查,最后再分享几个提高日常效率的进阶用法。无论你是刚入门的Linux新手,还是已经在线上环境维护服务的开发者,我都建议把SSH的每一个细节搞清楚,因为它直接决定了你的服务器安全底线。
1. 从服务器被爆破说起:为什么SSH安全问题必须认真对待
1.1 一次真实的暴力破解观察
那台服务器配置不高,就用来跑两个内部工具,系统是最常见的Linux发行版。初始状态基本是裸奔:22端口、密码登录、root可以直连。我最初的想法很简单——“不会有人注意到我这台小服务器”。但日志告诉我,这种想法错得离谱。
登录失败记录的数量在深夜开始飙升,每一条都是不同的IP地址,尝试的用户名先是root,然后是admin、test、ubuntu这类常见账号,密码组合也是常见的弱口令。有些IP在几分钟内连着尝试了几十次。攻击脚本显然在高速扫描全网段,逮到开放22端口的主机就丢字典爆破,根本不关心这台机器是个人项目还是大型平台。
当时给我留下两个印象:其一,SSH服务的默认配置在公网环境里几乎等于“不设防”;其二,安全这件事不是“会不会被盯上”,而是“什么时候被盯上”。从那以后,我再也没让任何新服务器以默认SSH配置直接暴露在公网。
1.2 SSH被攻破的后果远比想象中严重
有人会想:“我服务器里也没啥重要数据,被登录了又能怎样?”这种侥幸心理恰恰是最贵的学费。攻击者一旦拿到SSH登录权限,最常见的手段是植入挖矿程序占满你的CPU;更恶劣的是把数据打包加密,留下一段勒索信息;还有的会把机器变成攻击源,用它去扫描其他主机、发送垃圾流量。
即便最后你能重装系统挽回局面,中间损失的带宽、时间、被对方利用的窗口期,也已经够了。SSH协议本身做的事情就是三件事:身份认证、数据传输加密、保证会话完整性。它保护的是你最核心的后门入口,这个口子不守住,其他所有应用层的安全加固都会被轻易绕过。
我在每次内部培训里都会强调:SSH不是“一个能远程登录的工具”,而是“你信任每一台服务器之前,必须严格把守的第一道防线”。理解了这一点,后面所有的配置和加固才有意义。
2. 把sshd_config读薄:核心配置项每一行的作用
SSH服务的全部行为都集中在/etc/ssh/sshd_config这个文件里。新人最容易犯的错是网上搜一段配置直接粘贴,出了问题完全不知道是哪行导致的。我建议反过来:先把下面这份基线配置看懂,明白每个参数为什么存在,再动手改自己的服务器。
2.1 一份值得收藏的基线配置
# /etc/ssh/sshd_config 关键片段 Port 2222 # 修改默认端口,降低自动扫描命中率 PermitRootLogin no # 禁止root直接登录 PubkeyAuthentication yes # 开启密钥认证 PasswordAuthentication no # 关闭密码登录(务必先确保密钥可用) KbdInteractiveAuthentication no MaxAuthTries 3 # 每次连接最多允许3次认证尝试 MaxSessions 10 LoginGraceTime 30 # 登录宽限期30秒 ClientAliveInterval 60 # 每60秒向客户端发送一次保活心跳 ClientAliveCountMax 3 AllowUsers mysvc jumpadmin # 只允许这两个用户通过SSH登录提示:不要刚看到这份配置就直接照抄,尤其是“关闭密码登录”那一行。正确顺序是先完成密钥部署,确认能靠密钥登录,再执行
systemctl restart sshd,否则很容易把自己锁在服务器门外。
2.2 逐个理解关键参数背后的逻辑
先看Port。改端口不会让服务器变得更安全,攻击者只要扫全端口一样能找到你。它的真正价值是“降噪”:大量自动扫描只打22端口,改成高位端口后,绝大多数脚本会跳过你,日志清净很多。但你要时刻记得,真正的安全依仗是认证方式本身,而不是端口号。
然后是PermitRootLogin no。root是Linux系统里权限最大的账号,也是爆破脚本最优先尝试的对象。禁止root直连,改成先用普通用户登录再通过sudo提权,等于把攻击者拿到“第一把钥匙”之后仍需跨越一层权限边界,这是成本极低但收益极高的设置。
PubkeyAuthentication和PasswordAuthentication是一对组合拳。现代SSH都支持密钥认证,私钥相当于你随身携带的“印章”,远比一个可被猜测的密码安全。生产环境我的建议是:密钥认证打开、密码认证关闭。但要反复强调顺序:先测试密钥登录无误,再关密码认证,期间务必保持另一个登录会话在线备用。
MaxAuthTries限制每次连接的认证尝试次数。如果攻击者每次连接只能试三次,哪怕字典再大,也难以在一次TCP连接内穷举。LoginGraceTime则限制从建立连接到完成登录的时间,防止大量慢速半开连接占用sshd进程资源。
AllowUsers是白名单机制,只有当列表里的用户名才能登录,这比“拒绝陌生IP”更直接。如果团队里有频繁变动,用AllowGroups也可以,把允许登录的用户统一放进一个系统用户组,管理起来更方便。另外ClientAliveInterval和ClientAliveCountMax合起来是探活机制:服务器每60秒确认一次客户端还活着,连续3次没有响应就主动断开。这个配置能避免大量僵死会话占用资源,同时让一些“办公网络断网后状态一直挂着”的SSH连接被及时回收。
我还经常被问到Protocol 2。现代OpenSSH已经默认只支持SSH协议第二版,老旧的协议第一版因为存在严重漏洞被彻底淘汰,所以这个参数现在绝大多数情况下不需要显式配置。
3. 密钥认证从零落地:亲手搭一套“无密码登录”
密钥认证的原理不复杂:你生成一对密钥,一个放服务器上作为公钥,一个留在本地作为私钥。登录时服务器会拿公钥验证你的私钥,验证通过即放行。整个过程私钥永远不出本地机器,因此比密码更抗“钓鱼”和“窃听”。
3.1 生成密钥时如何选择类型和参数
在本地机器上执行:
ssh-keygen -t ed25519 -C "work-laptop-2024"这里我推荐ed25519。它的优点是密钥短、生成快、安全性足够强,也是当前OpenSSH很成熟的主流选择。如果你的服务器还是好几年前的老系统,OpenSSH版本过低不支持ed25519,那就退一步用经典方案:
ssh-keygen -t rsa -b 4096 -C "work-laptop-2024"生成过程中会让你确认保存路径和设置passphrase。这里的passphrase建议一定要设——它相当于私钥的最后一道锁,即使你的本地文件被拷贝走,没有passphrase也无法使用。执行完,~/.ssh目录下会出现两个文件:id_ed25519是私钥,id_ed25519.pub是公钥。后者就是可以放到服务器上的内容。
3.2 把公钥部署到服务器
最简单的做法是用ssh-copy-id:
ssh-copy-id -i ~/.ssh/id_ed25519.pub mysvc@192.0.2.10 -p 2222这条命令会临时用密码登录一次,然后把你的公钥追加到服务器上~/.ssh/authorized_keys文件里,并顺手把权限修正成SSH要求的状态。如果不想用这个工具,也可以手动登录服务器,把公钥内容追加进authorized_keys:
mkdir -p ~/.ssh && chmod 700 ~/.ssh echo "ssh-ed25519 AAAA...your-public-key..." >> ~/.ssh/authorized_keys chmod 600 ~/.ssh/authorized_keys很多新人在这步容易踩坑,典型症状是密钥文件放上去了,但登录时依然报Permission denied (publickey)。八成是权限问题:.ssh目录权限必须是700,authorized_keys文件权限必须是600,家目录本身也不能是group或其他用户可写状态。此外,如果服务器开了SELinux,可能还需要执行一次restorecon -R -v ~/.ssh,否则权限符合常规要求但SSH依然拒绝。
部署完成后,另开一个终端测试:
ssh -i ~/.ssh/id_ed25519 mysvc@192.0.2.10 -p 2222提示输passphrase后能正常登入,就算部署成功。此时再回头修改PasswordAuthentication no重启服务,你的服务器才进入“密码爆破无效”状态。
3.3 多台服务器、多个密钥如何管理
如果你和我一样要维护十几台服务器,可能会出现“每台机器一套密钥”的混乱局面。我的方案是用~/.ssh/config文件做统一管理,为每台机器定义一个别名:
Host work HostName 192.0.2.10 User mysvc Port 2222 IdentityFile ~/.ssh/id_ed25519 IdentitiesOnly yes Host jump HostName 198.51.100.20 User jumpadmin Port 2233 IdentityFile ~/.ssh/id_ed25519_jump IdentitiesOnly yes配置完之后,直接用ssh work就能登录对应机器,不用再记IP和端口。IdentitiesOnly yes的意思是让客户端只尝试这里指定的密钥,而不是把本地所有私钥都试一遍,可以避免多余的认证交互和“服务器端尝试密钥过多被拒绝”的尴尬。
这里还要提醒一点:私钥文件千万别往代码仓库里传,也别随手拷给同事。它就像你家的钥匙,丢了就要去所有配置过对应公钥的服务器上移除公钥。
4. 加固实测:把默认SSH服务逐步改造成抗扫描方案
配置项单看容易头晕,我按自己实际加固一台Ubuntu服务器的过程,整理出一套可照抄的流程。整个过程按顺序做完大约半小时,收益却能持续很久。
4.1 第一步:先摸清当前状态再动手
上线新服务器或者接手一台旧机器,先别急着改配置。我通常按如下顺序确认现状:
systemctl status sshd grep -E "^(Port|PermitRootLogin|PasswordAuthentication|PubkeyAuthentication)" /etc/ssh/sshd_config ss -tlnp | grep -E ':22|:2222'这几条命令能告诉你三件事:sshd服务是否正常在跑、当前的关键认证策略是什么、正在监听哪些端口。如果是刚装好的系统,我推荐在第一时间完成加固,不要留“先上线后面再补”的空窗期。
4.2 第二步:修改监听端口并放行防火墙
假设要把SSH官方默认的22端口改成2222,在sshd_config里修改Port之后,必须检查两层放行:一是本机防火墙,二是云服务商安全组。常见的本机防火墙操作如下:
sudo ufw allow 2222/tcp sudo ufw enable如果你用的是firewalld,对应的命令是:
sudo firewall-cmd --permanent --add-port=2222/tcp sudo firewall-cmd --reload云服务商的安全组也要登录控制台,把入方向的2222/TCP端口加进放行列表。这一步极其容易漏掉,我见过不少同学在本地改完端口后,直接断开旧连接,发现新端口连不上,而sshd明明已经重新加载了。排查到最后,就是安全组没有放行。所以每次动端口,我一定会先在另一个终端测试新端口能建立连接,再动手改配置。
4.3 第三步:密钥登录通过后,再关闭密码登录
这个流程我在前面已经反复强调顺序:先生成密钥 → 用ssh-copy-id部署公钥 → 新终端测试密钥登录成功 → 再修改sshd_config关掉密码登录。顺序绝对不能颠倒。
关闭密码登录后的配置至少要包含这三行:
PermitRootLogin no PubkeyAuthentication yes PasswordAuthentication no重启服务使用systemctl restart sshd,但重启前务必执行一次语法校验:
sudo sshd -tsshd -t会检查配置文件的语法和参数是否合法,如果有问题它会提示具体哪一行出错。养成这个习惯后,我几乎再也没遇到过“改配置导致服务起不来”的险情。
4.4 第四步:用fail2ban加固第一道防线
就算关闭了密码登录,只要sshd还允许密码认证的预留通道(例如某些服务必须保留一部分密码登录用户),暴力破解就依然存在。我更推荐把fail2ban装好,它会自动监控SSH登录日志,在短时间内超过阈值就临时封禁对方IP。
安装方式:
sudo apt install fail2ban # Debian/Ubuntu sudo yum install fail2ban # CentOS/RHEL默认配置就已经不错,但我会在/etc/fail2ban/jail.local里针对sshd增加自定义参数:
[sshd] enabled = true port = 2222 filter = sshd logpath = /var/log/auth.log maxretry = 3 findtime = 600 bantime = 3600含义是:10分钟内同一IP出现3次认证失败,就封禁1小时。实际运行后的感受非常明显:原本刷屏一般的failed password会迅速消失,大量扫描IP在几次尝试后就被封进了iptables规则,日志恢复安静。fail2ban不解决所有问题,但它能让攻击成本显著上升,值得作为必装项。
4.5 第五步:白名单、空密码清理和其他细节
最后我还会再确认几个边界项:
PermitEmptyPasswords no:禁止空密码账号登录;- 用
AllowUsers锁用户范围,例如AllowUsers mysvc jumpadmin,不在列表里的用户即使存在也无法通过SSH登录; - 定期检查
/home/*/authorized_keys,如果发现有不明公钥,立即排查来源。
全部加固完成后,可以再观察一轮日志。通常情况是:新端口加密钥登录加fail2ban三件套一起用上之后,登录失败日志量会从“每分钟几十条”降到“几天一条”。到这一步,你的SSH服务才算真正从“裸奔”变成了“有防御纵深”。
5. SSH连不上的排障链路:从几个高频报错说起
配置再怎么完善,总有连不上的时候。这一节我按自己实际排查SSH故障的路径,把最常见的几类问题拆开讲。
5.1 高频报错速查表
| 客户端报错 | 常见原因 | 排查路径 |
|---|---|---|
| Connection refused | sshd没运行、端口填错、防火墙拒绝 | 查服务状态、查监听端口、查安全组 |
| Connection timed out | 网络不通、安全组未放行、防火墙DROP | 从客户端逐步ping、telnet端口 |
| Permission denied (publickey) | 公钥未部署、私钥不对、权限问题、服务端策略改变 | 用ssh -v看过程,查服务端auth.log |
| Host key verification failed | known_hosts中主机指纹变化 | 确认服务器没被重装后清理known_hosts |
| 输入密码后仍被断连 | 服务端MaxAuthTries、账号被锁、登录Shell异常 | 查看auth.log,检查账号状态 |
5.2 “Connection refused”到底是谁拒绝了
这个提示通常说明TCP连接已经被另一端主动拒绝,也就是“有主机响应但端口没服务”。我一般分三步查:
systemctl status sshd ss -tlnp | grep ssh sudo tail -n 50 /var/log/auth.log如果sshd没在运行,就重新启动并排查为什么挂了。如果sshd在运行但监听端口不是你要连的那个,说明Port配置或客户端-p参数至少有一边不对。还要注意云服务器上有些安全组会明确返回“拒绝连接”而不是直接丢包,让人误以为服务没起,实际上就是防火墙只对指定端口放行。
5.3 “Connection timed out”和看不见的防火墙
和前者不同,Connection timed out意味着数据包被静默丢弃,通常是防火墙策略或安全组规则没放行。排查时先在本地测:
ping 192.0.2.10 nc -vz 192.0.2.10 2222ping不通先查网络路由,ping通但nc提示超时则必是防火墙级别拦截。此时去云控制台看安全组入站规则,再去服务器上看ufw/firewalld规则。我遇到过一个很隐蔽的案例:服务器本机防火墙放行了2222,安全组也放行了2222,但客户端所在网络环境把非标准端口出站全部拦了,换一台设备马上连上。这提醒我,SSH故障不一定只在服务器端,客户端本地的网络策略同样要考虑。
5.4 “Permission denied”并不是随便试试就行
密钥认证失败的排查,最有效的办法是开详细日志:
ssh -vvv -i ~/.ssh/id_ed25519 mysvc@192.0.2.10 -p 2222然后重点看输出的这些信息:客户端是否发送了正确的公钥文件,服务端是否接受了这个公钥,最终是卡在了密钥验证还是被PasswordAuthentication拒绝。服务端同时查日志:
sudo tail -n 30 /var/log/auth.log常见的根因我列在下面:
- authorized_keys里的公钥内容和本地
id_ed25519.pub不一致; .ssh或authorized_keys权限过宽,服务端直接忽略该文件;PubkeyAuthentication被意外关闭;AllowUsers里没有包含当前登录用户;- 用户家目录位于NFS/共享目录,SELinux上下文不正确。
多数情况下,问题在权限和文件路径上。先确认~/.ssh/authorized_keys确实存在且属于当前登录用户,再照前面提到的方式修权限,八成能解决。
5.5 “连接很慢”的问题出在DNS和GSSAPI
有时候密码或密钥都能过,但输入命令后要卡好几秒才出现登录提示壳。这个在旧系统上特别常见,两大元凶是UseDNS和GSSAPIAuthentication。sshd默认可能对客户端IP做反向DNS解析,解析超时才放行;GSSAPI认证也会在客户端发起时产生额外的交互延迟。解决很直接:
UseDNS no GSSAPIAuthentication no改完重启服务,登录延迟通常会立刻降下来。
5.6 一次“改配置后全员连不上”的完整复盘
有一次我为了收紧权限,在sshd_config里加了AllowUsers,结果里面漏掉了运维账号,自己先测的时候没发现问题,过了几分钟另一个同事说连不上了。一查日志,全是“User xxx not allowed because not listed in AllowUsers”。还好当时没断开我自己的会话,马上把正确的用户名补回去,systemctl reload sshd恢复。
这事的教训有三条:一是改任何认证相关配置前,先备份原始文件,例如cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak;二是改动后用sshd -t必查一次;三是一旦涉及登录权限的收窄,永远记得先用最保守的方式确认“自己还能登录”,再处理其他用户。很多线上事故都是改配置一时爽、恢复配置火葬场。
6. 日常运维里SSH的高阶用法:管理效率和安全并存
SSH不止是“远程登录终端”,它还是一套可以大大提升日常运维效率的安全工具组。下面这几个用法,能让你少记很多命令、少开很多工具。
6.1 文件传输:scp、sftp和rsync
远程传文件我很少用网盘或FTP,直接用scp:
scp -P 2222 ./backup.tar.gz mysvc@192.0.2.10:/home/mysvc/需要增量同步目录时,rsync更合适:
rsync -avz -e "ssh -p 2222" ./data/ mysvc@192.0.2.10:/home/mysvc/data/sftp则适合需要交互式浏览、上传、下载的场景,连接方式与ssh几乎一样。它们底层都走SSH加密,不需要额外开放其他端口,也不怕数据在网络上裸奔。这个组合的好处是:一条SSH通道把登录、传输、同步全包了,少暴露一个服务就少一个被攻击面。
6.2 端口转发:安全访问内网服务
SSH还有一个非常实用的能力叫本地端口转发。比如某台内网机器上的数据库只监听127.0.0.1端口,你本地的管理工具又需要连它,就可以这样:
ssh -N -f -L 8080:127.0.0.1:5432 mysvc@192.0.2.10 -p 2222执行之后,访问本机的8080端口,数据会通过SSH加密通道转发到192.0.2.10这台服务器的5432端口。这类用法很适合连接内网数据库、管理后台、开发调试接口,前提是确认接收端是你自己有权限访问的服务,并且不要把这个本地端口暴露给其他人使用。
另一种是远程端口转发,让外部客户端通过指定端口访问本机或内网某个服务,运维中用于临时暴露本地开发环境给同事联调,同样要走安全授权并限定时效。总的原则是:端口转发是提升生产力和解决网络隔离的利器,但绝不能把本机服务意外暴露给不信任的网络。每次用完及时关掉,并检查本机监听了哪些额外端口即可。
6.3 一台客户端管多台服务器:连接复用与会话保持
如果经常在几台机器之间来回操作,每次重新连接都要重新握手、认证,浪费时间不说还容易打断思路。我在~/.ssh/config里维护了连接复用配置:
Host * ControlMaster auto ControlPath ~/.ssh/sockets/%C ControlPersist 300它会让同一客户端到同一服务器的连接被复用,后续再开新SSH窗口时能直接复用已有连接,提速明显。对应的,还需要先建好socket目录:
mkdir -p ~/.ssh/sockets远程执行长任务时,常配合tmux或screen使用。先登录服务器,然后起一个tmux会话,在里面跑备份、部署或者编译任务。就算本地网络断开,重连后执行tmux attach就能回到刚才的会话,任务不会被中断。这个习惯帮我避免过无数次“网络一闪,任务白跑”的惨剧。
6.4 审计登录行为和检查钥匙
最后讲一个很多人忽略的习惯:定期审计SSH的登录情况。我喜欢用这几条命令做轮检:
sudo grep "Accepted publickey" /var/log/auth.log | tail -n 20 cat ~/.ssh/authorized_keys last -a | head -n 20第一条看最近有哪些公钥成功登录,第二条确认自己的授权清单,第三条看最近登录时间和来源IP。如果发现来源异常或者多出不明公钥,马上追查。密钥文件是信任凭证,需要像对密码一样对待,定期清点是维护SSH服务健康的关键一环。
写到这,我把这些年积累的SSH服务心得都过了一遍。最后想特别分享一个救命方法:我会给每一台重要服务器额外预留一个备用监听端口,并在监控里加上一条告警规则,保证至少有一个端口不会因为常规维护被人为改动卡住。紧急时刻,这个备用入口能让你在误改配置后仍有路可退。另外,每次动sshd_config之前,先执行一遍sshd -t语法检查再重启,真的是成本最低、收益最稳的操作习惯。SSH服务虽然看起来独立,但它连接着你手里所有服务器的控制和数据,值得用最认真的态度对待。