把SSH登录从密码认证切换到密钥认证,是加固Ubuntu服务器最直接有效的手段。很多服务器被攻破,根源就是弱口令被暴力破解或者撞库成功。关闭密码认证后,攻击者即使知道你的用户名,没有对应的私钥也登不进来,这等于直接堵死了最大的一类攻击面。

确认当前SSH服务状态

动手之前,先看一眼当前SSH服务的运行状态和版本。用下面这条命令检查sshd是否在运行:

systemctl status sshd

如果显示active (running),说明服务正常。接着用ssh -V看一下版本,确保你用的是OpenSSH 7.0以上的版本,因为后续要用到的部分配置指令在老版本里语法不同。Ubuntu 20.04和22.04默认的OpenSSH版本都满足要求,但如果你维护的是较旧的16.04或18.04系统,需要额外留意。

在本地生成SSH密钥对

密钥对必须在自己的本地电脑上生成,绝不要在服务器上生成然后把私钥下载下来,那样私钥已经有过一次网络传输风险。Windows用户可以在PowerShell或CMD里直接使用ssh-keygen命令,macOS和Linux用户打开终端即可。推荐使用ed25519算法,它比RSA更快且同等安全强度下密钥更短:

ssh-keygen -t ed25519 -C "your-comment-here"

命令会提示你选择保存路径和设置私钥的加密密码。这个密码是用来保护私钥本身的,就算私钥文件泄露,没有这个密码也无法使用。生产环境建议一定设置一个强密码。生成完毕后,你会得到两个文件:id_ed25519是私钥,自己保管好;id_ed25519.pub是公钥,需要上传到服务器。

把公钥部署到Ubuntu服务器

最省事的方法是用ssh-copy-id命令,它会自动把公钥追加到目标用户家目录下的~/.ssh/authorized_keys文件中,并设置正确的权限:

ssh-copy-id -i ~/.ssh/id_ed25519.pub username@your-server-ip

这条命令执行时会要求输入一次目标用户的密码,因为此时密码认证还开着。如果你手动创建了~/.ssh目录和authorized_keys文件,一定要把权限锁死,否则SSH会出于安全考虑拒绝读取:

mkdir -p ~/.ssh
chmod 700 ~/.ssh
touch ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys

然后把公钥内容粘贴进authorized_keys文件即可。注意,很多配置失败的情况都是因为.ssh目录或authorized_keys文件的权限过大,比如设成了777或644,SSH服务检测到后直接忽略该文件。

验证密钥登录是否生效

在关闭密码认证之前,务必另开一个终端窗口,用密钥方式尝试登录一次:

ssh -i ~/.ssh/id_ed25519 username@your-server-ip

如果直接登进去了,说明密钥配置无误。如果提示输入私钥密码,输入后也能登入,同样没问题。这一步绝对不能跳过,否则一旦你关闭密码认证而密钥又配错了,就会彻底被锁在服务器外面,只能通过VNC或带外管理控制台救回来。

修改SSH服务端配置文件

Ubuntu的SSH服务主配置文件位于/etc/ssh/sshd_config。用你熟悉的编辑器以root权限打开它:

sudo nano /etc/ssh/sshd_config

需要关注和修改的核心指令有以下几条。第一,确保公钥认证处于开启状态:

PubkeyAuthentication yes

第二,关闭密码认证,这是本次加固的核心目标:

PasswordAuthentication no

第三,关闭挑战响应认证,这也是一种基于密码的交互方式,同样需要禁掉:

ChallengeResponseAuthentication no

第四,禁止PAM模块中的密码认证环节,有些系统即使PasswordAuthentication设为no,PAM仍可能允许密码登录,所以这一条也要明确关闭:

UsePAM yes

在UsePAM yes的情况下,还需要额外处理PAM层面的密码控制。编辑/etc/ssh/sshd_config时,可以同时加入或取消注释下面这行,彻底切断PAM的密码认证路径:

KbdInteractiveAuthentication no

在较新的OpenSSH版本中,KbdInteractiveAuthentication实际上替代了ChallengeResponseAuthentication,所以建议两条都设为no,避免版本差异导致配置不生效。

禁止root用户通过SSH登录

这是一个与密钥认证同样重要的安全实践。即使你用了密钥登录,也不应该允许root直接通过SSH登入,所有操作都应该通过普通用户sudo提权完成。在sshd_config中找到PermitRootLogin指令,将其设为no:

PermitRootLogin no

如果某些自动化场景确实需要root身份运行远程脚本,可以设置为prohibit-password,这样root也只能用密钥登录。但更推荐的做法是用普通用户配合sudoers规则来细化权限控制。

可选但推荐的额外加固参数

除了核心的认证方式切换,下面这些配置可以进一步提升SSH服务的安全性。限制允许登录的用户,只让特定的管理员账户能够SSH进去:

AllowUsers your-admin-user

修改默认的22端口,虽然不算是真正的安全措施,但能过滤掉绝大多数自动化扫描和低水平攻击流量:

Port 2222

如果改了端口,记得在云服务商的安全组或系统防火墙里放行新端口。设置空闲超时断开连接,防止管理员离开终端后会话被他人利用:

ClientAliveInterval 300
ClientAliveCountMax 0

这两行的意思是每300秒发送一次心跳包,如果客户端没有响应,立即断开连接。禁止空密码登录和禁用主机名反向解析也能减少一些不必要的暴露面:

PermitEmptyPasswords no
UseDNS no

UseDNS设为no可以显著加快SSH登录速度,因为服务端不再对每个连接尝试反向DNS查询。

重启SSH服务并保留当前会话

配置修改完毕后,先不要退出当前的SSH会话。用下面命令检查配置文件语法是否正确:

sudo sshd -t

如果没有任何输出,说明语法没问题。然后重启SSH服务使配置生效:

sudo systemctl restart sshd

现在最关键的一步来了:保持当前已登录的会话不要断开,另外再开一个终端窗口尝试用密钥登录。如果新窗口能成功登入,说明配置完全正确。如果新窗口登不进去,你还有当前这个已经登入的会话可以用来回滚配置。这是防止自己被锁在外面的最后一道保险。

配置系统防火墙协同防护

如果服务器上启用了ufw防火墙,确保SSH端口被正确放行。如果改过端口,记得更新规则:

sudo ufw allow 2222/tcp
sudo ufw enable
sudo ufw status verbose

很多人在改完SSH端口后忘记更新云平台的安全组规则,导致新端口被拦截,这一点要特别注意。云服务器的网络层面通常有两层防火墙:一层是操作系统内部的iptables或ufw,另一层是云服务商提供的安全组。两层都要放行新端口。

私钥的保管与轮换策略

密钥认证虽然安全,但私钥的管理变成了新的风险点。私钥文件应该存放在加密的磁盘卷上,不要上传到任何网盘、聊天工具或代码仓库里。建议为不同的服务器或服务器组使用不同的密钥对,这样即使一把私钥泄露,影响范围也有限。定期轮换密钥是个好习惯,流程也不复杂:生成新密钥对,把新公钥追加到authorized_keys,测试新密钥可用后,在authorized_keys里删除旧公钥对应的那一行即可。

多因素认证的叠加考虑

对于安全要求极高的环境,单靠密钥认证还不够。可以叠加使用Google Authenticator之类的TOTP二次验证模块,或者使用U2F硬件密钥。Ubuntu上安装libpam-google-authenticator包并配置PAM模块,就能在密钥认证之后再要求输入动态验证码。这样一来,即使私钥和私钥密码同时泄露,攻击者没有你的手机或硬件令牌也登不进来。不过引入多因素认证会增加运维复杂度,需要根据实际风险评估来决定是否部署。

日志监控与入侵检测

配置做完不等于安全工作的结束。你需要持续监控SSH的登录日志,及时发现异常尝试。Ubuntu的SSH登录记录默认写入/var/log/auth.log,可以用以下命令查看最近的登录情况:

sudo grep "sshd" /var/log/auth.log | tail -20

更高效的做法是部署fail2ban,它能自动分析日志,对频繁尝试登录失败IP进行临时封禁。即使你已经关闭了密码认证,fail2ban仍然能帮你挡掉大量扫描流量,减少日志噪音。安装和启用fail2ban非常简单:

sudo apt update
sudo apt install fail2ban -y
sudo systemctl enable fail2ban --now

默认的jail配置对SSH已经生效,封禁策略为10分钟内失败5次则封禁10分钟。你可以根据自己的需求在/etc/fail2ban/jail.local中调整这些参数。

常见问题排查思路

如果配置后密钥登录失败,最常见的几个原因如下。authorized_keys文件权限不是600,或者.ssh目录权限不是700,SSH会直接忽略公钥。检查权限用ls -la ~/.ssh/。家目录本身的属主和权限也有要求,家目录不能给组用户或其他用户写权限,否则SSH同样会拒绝。用ls -ld ~查看,权限应该是755或700,属主必须是用户自己。SELinux或AppArmor策略也可能阻断密钥读取,Ubuntu默认使用AppArmor,一般不会主动拦截SSH的常规操作,但如果你的系统经过定制加固,需要检查AppArmor的审计日志。还有一个容易被忽略的点:如果你用的是ed25519密钥,但服务器上的OpenSSH版本太老不支持这个算法,也会失败。这种情况换用RSA 4096位密钥即可兼容。

维护窗口与自动化部署

如果你管理着多台Ubuntu服务器,手动一台台去改配置显然不现实。可以用Ansible、Puppet或Chef这类配置管理工具批量推送公钥和sshd_config。在自动化脚本里,一定要包含配置语法检查和回滚逻辑。比如Ansible的playbook中先用validate参数检查sshd配置,再执行restart,并且设置合理的错误处理机制,避免因为配置错误导致大批量服务器同时失联。对于云上的自动伸缩组,应该把密钥部署和SSH加固写进启动模板或用户数据脚本里,确保新扩容出来的实例自动应用安全配置。

把Ubuntu服务器的SSH密码认证彻底关闭,只保留密钥登录,是基础安全建设里性价比最高的操作之一。它几乎不消耗系统资源,不影响正常使用体验,却能挡住绝大多数自动化攻击。配合禁止root登录、修改默认端口、部署fail2ban以及严格的私钥管理,你的服务器在公网上的暴露风险会大幅降低。安全从来不是一次性动作,定期审计日志、轮换密钥、更新系统补丁,才能让这条防线持续有效。