SSH配置与安全加固:密钥认证、权限管理与故障排查
2026/9/11 9:57:42 网站建设 项目流程

前几天帮朋友排查一台 Ubuntu 服务器,现象特别典型:密码明明输入正确,SSH 却一直报 Permission denied,最后查出来是 /root/.ssh 目录权限多了一个组的读权限,把 authorized_keys 整个“废”掉了。这种坑我踩过不止一次,所以今天干脆把 SSH 配置和保护这件事从头到尾拆一遍,讲清楚原理、给出可复制的操作,再说说那些文档里不会写的排查经验。

这篇文章覆盖三个层面:怎么把 SSH 配好、怎么用密钥实现免密登录、怎么在暴露到公网之后把安全防护做实。如果你是刚接触服务器的小白,可以跟着步骤一步步做;如果你已经用了很久 SSH 但偶尔被一些诡异问题卡住,那直接跳到常见问题排查那一节,大概率能找到你想要的答案。

1. 先看本质:SSH 的加密与认证在解决什么问题

1.1 一条命令背后的三次握手

很多人把 SSH 当成“远程命令行工具”,其实它解决的问题比命令行本身重要得多。你通过 SSH 登录服务器,中间传输的所有内容都是加密的——包括你输入的密码、执行的命令、复制的文件内容。如果不加密,你在网络上发出去的每一个字符都可能被中途截获,这在公网环境下等于裸奔。

SSH 建立连接时做了三件事:协商加密算法、验证服务器身份、验证用户身份。第一次连接服务器时看到的指纹提示,就是在验证服务器身份。那个指纹是服务器主机公钥的哈希值,你确认一次之后会被记入本机的 known_hosts 文件,之后再连接就不会重复询问。如果某一天服务器的系统重装了,主机密钥变了,客户端就会提示 REMOTE HOST IDENTIFICATION HAS CHANGED,这时候你就得小心——可能是真的重装了系统,也可能是有人冒充了你的服务器。确认原因之前,不要贸然清掉 known_hosts 里的记录。

1.2 密码认证和密钥认证的差别

很多人觉得“我有密码,所以我很安全”,这是个误区。密码认证的问题在于:你输入的密码会通过网络传输到服务器进行比对,虽然有加密保护,但服务器端拿到的是密码本身,一旦服务器被攻破或日志记录不当,密码就可能泄露。而且密码容易被暴力破解——你设一个 8 位纯数字密码,在公网机器上可能活不过一天,因为脚本会不停地尝试常见弱口令。

密钥认证不一样。客户端持有一把私钥,服务器上存放对应的公钥。登录时服务器会发送一个随机挑战,客户端用私钥对这个挑战签名,服务器用公钥验证签名是否有效。整个过程私钥永远不出现在网络上,服务器也不需要知道你的私钥内容,它只需要验证你确实持有这把私钥的对应公钥。你可以把公钥理解成一把锁,私钥是唯一的钥匙,锁放在服务器上,钥匙留在你自己手里。

这也是为什么几乎所有云厂商都推荐用户关闭密码登录、只保留密钥登录——不是为了折腾你,而是这套机制在安全性上确实有本质区别。

2. 从零开始:SSH 服务端的基础配置

2.1 安装与首次启动

绝大多数 Linux 发行版默认不装 SSH 服务端,只有客户端。如果你在一台新服务器上 ssh localhost 提示 Connection refused,那大概率是 openssh-server 还没装。

Debian/Ubuntu 系用:

sudo apt update sudo apt install openssh-server sudo systemctl enable --now ssh

RHEL/CentOS/Rocky 系用:

sudo yum install -y openssh-server sudo systemctl enable --now sshd

装完之后先确认端口已经监听:

ss -tlnp | grep 22

看到 0.0.0.0:22 或 [::]:22 的 LISTEN 状态就说明服务起来了。这里有个容易忽略的点:如果你有防火墙(ufw、firewalld 或云平台安全组),22 端口默认也是不通的。很多时候“SSH 连不上”根本不是 SSH 的问题,而是防火墙没放行。排查链路一定是先看服务是否起来,再看端口是否监听,再看防火墙是否放行。

2.2 sshd_config 里的关键参数

SSH 服务端的核心配置都在 /etc/ssh/sshd_config 这个文件里。每次修改完,先检查语法再重载服务:

sudo sshd -t sudo systemctl reload ssh

sshd -t 这一步必须养成习惯,它能把配置文件的语法错误提前暴露出来,避免 reload 之后把自己锁在门外。

下面这几个参数是配置时的重点:

参数默认值说明
Port22监听端口,改成非默认端口可减少扫描
ListenAddress0.0.0.0限制监听地址,比如只监听内网 IP
PermitRootLoginprohibit-password是否允许 root 登录,建议禁止密码登录
PasswordAuthenticationyes是否启用密码认证,加固阶段改为 no
PubkeyAuthenticationyes是否启用密钥认证,保持 yes
MaxAuthTries6单次连接最大认证尝试次数,建议改小
ClientAliveInterval0服务端向客户端发送保活包间隔
ClientAliveCountMax3客户端无响应多少次后断开

Port 改成非 22 只是个“降低暴露面”的手段,它不是安全的核心,但确实能帮你躲掉一大波无脑扫描脚本。改成 22022 或者 50022 这类端口后,你的 auth.log 里恶意尝试会少很多。注意改完端口后云安全组、防火墙规则要同步改,否则下一次连接就会超时。

2.3 防火墙与云安全组的联动配置

刚才说的防火强放行,这里展开讲讲。如果你用 ufw:

sudo ufw allow 22/tcp sudo ufw enable sudo ufw status

如果用 firewalld:

sudo firewall-cmd --permanent --add-port=22/tcp sudo firewall-cmd --reload

如果你在云服务器上,还要去云控制台的安全组规则里放行对应端口。很多人的顺序搞反了:先在安全组放行 22,然后改了 SSH 端口,安全组忘了改,结果新端口连不上,只能去控制台用网页终端救急。所以改端口和改防火墙要做到一步到位,改完立刻测试,测试通过再关旧端口。

还有一个容易踩的坑:IPv6 环境下你要同时放行 tcp6 的端口,否则你用 IPv6 地址连接会一直卡在 Connection timed out。防火墙规则要明确写清是 IPv4 还是 IPv6,避免出现“同网段机器能连,另一台连不上”的诡异现象。

3. 密钥认证:真正好用的免密登录

3.1 生成密钥对:ed25519 还是 RSA?

生成密钥用的命令很简单,但选型有讲究:

ssh-keygen -t ed25519 -C "your_comment"

现代系统我都推荐用 ed25519,它的密钥短、速度快、安全性足够强。有些老旧的 Git 服务器、设备(比如部分交换机、老版本 OpenSSH)可能不支持 ed25519,这时候才需要退回 RSA:

ssh-keygen -t rsa -b 4096 -C "your_comment"

生成过程中会让你输入 passphrase(私钥口令)。这个 passphrase 是私钥的额外保护层:即使你的私钥文件被拷走,没有 passphrase 也没法用它。嫌麻烦可以直接留空,但从安全角度我建议设置一个,然后把私钥文件放到安全的位置。

生成完成后,会在你的用户目录下生成 .ssh 目录,里面有 id_ed25519(私钥)和 id_ed25519.pub(公钥)。私钥永远留在本地,公钥可以分发到你需要登录的服务器上。

3.2 部署公钥到服务器

把公钥放到服务器上有一个专用命令:

ssh-copy-id -i ~/.ssh/id_ed25519.pub user@server_ip

这条命令会自动把公钥追加到服务器上对应用户的 ~/.ssh/authorized_keys 文件,并且处理好权限。如果你因为某些原因没法用 ssh-copy-id,手动操作也完全可以:在本机执行 cat ~/.ssh/id_ed25519.pub 复制输出内容,然后在服务器上编辑 ~/.ssh/authorized_keys,把内容粘贴进去。

这里有一个极其关键的细节:权限必须严格。.ssh 目录权限应该是 700,authorized_keys 文件权限应该是 600。权限只要多一个“组可读”,SSH 服务就会认为文件不安全,直接忽略这个文件,表现为“密钥没生效”“还是让输密码”。

chmod 700 ~/.ssh chmod 600 ~/.ssh/authorized_keys

如果你登录的是 root,还要检查 root 用户的家目录本身不能被组或其他用户写,否则同样会被拒绝。这个权限问题是我见过最多的 SSH 配置坑,没有之一。

3.3 用 config 文件管理多台服务器

当你管理的服务器多了之后,每次输入 ssh user@ip -p port 会让人崩溃。建议在本地 ~/.ssh/config 里定义主机别名:

Host myserver HostName 203.0.113.10 User admin Port 22022 IdentityFile ~/.ssh/id_ed25519

配置完之后,直接 ssh myserver 就能连上,不用记 IP 和端口,也省去了指定密钥文件的麻烦。如果你有多台机器,可以照葫芦画瓢写多个 Host 块,相当于给每台机器建了个通讯录。这里我体验最明显的场景是配合 VSCode Remote SSH 使用——在 VS Code 里连接远程服务器时,它会读取这套 config 配置,你在编辑器里输入别名就能直接连上,配合 Remote 插件还能直接在服务端写代码跑程序,调试体验非常顺。

3.4 场景补充:Git、群晖和 Windows

密钥认证不止用于登录 shell,Git 的远程仓库操作也是同一套机制。GitHub、GitLab 都支持把公钥添加到账户里,添加之后你 clone、push 就不用反复输入账号密码了。GitLab 里添加密钥的位置在 Preferences -> SSH Keys,GitHub 在 Settings -> SSH and GPG keys。值得留意的是,不同平台的机器如果换了电脑,重新生成密钥再添加一遍即可,旧密钥可以随时删除或作废,管理成本很低。

如果你用群晖 NAS,配置 SSH 密钥的思路也一样:控制面板打开 SSH 功能,然后把公钥写到对应用户的 ~/.ssh/authorized_keys 里。群晖的 DSM 系统对目录权限有自己的管理方式,在 /root/.ssh 和 /volume1/homes/用户/.ssh 这两种路径之间容易搞混,建议先确认你登录的到底是哪个用户,再决定公钥写到哪个家目录下,不然你会一直卡在“明明配置了密钥还是要输密码”。

Windows 上的场景也很常见。Bitvise SSH Server / Client 是老牌的 Windows 端 SSH 工具,但如果你用的 Windows 10 1809 以上版本,系统自带的 OpenSSH 客户端已经足够,直接在 PowerShell 里执行 ssh、ssh-keygen、ssh-copy-id 就能搞定大部分需求。Windows 上配置 ~/.ssh/config 的路径和 Linux 一致,只是家目录默认在 C:\Users\用户名。

4. 安全加固:让不该进来的人进不来

4.1 关闭密码登录与 root 远程登录

核心加固动作是在确认密钥登录已经可用之后,修改 sshd_config:

PermitRootLogin prohibit-password PasswordAuthentication no

这时候你告诉服务器两件事:第一,root 用户不能直接远程密码登录;第二,所有用户都不能用密码登录,只能用密钥。一定要提前在另一个终端窗口测试密钥登录没有问题,再保存并重载配置,否则你可能会把自己锁在门外。我亲眼见过有人在同一台机器上改完配置就断开了连接,结果密钥没配好,最后只能通过云控制台重置密码救场。

如果你需要 root 权限,正确的用法是用普通用户登录,再 su 或 sudo 提权。这样在审计日志里你能看到谁什么时候登录过,哪个用户执行了 root 操作,不至于所有人都拿 root 随意造。

4.2 减小暴露面与限制登录来源

除了关闭密码登录,下面这些做法能进一步减小暴露面:

  • 修改默认端口,让扫描脚本找不到入口。
  • 限制 ListenAddress,只监听内网 IP,完全不对公网开放。
  • 用 AllowUsers 只允许指定用户登录,其他用户一律拒绝:
AllowUsers admin ops
  • 用 AllowGroups 限制登录用户组,更便于团队管理:
AllowGroups sshusers
  • 把 MaxAuthTries 改成 3,避免攻击者在一个连接里反复试密码。
  • 设置 ClientAliveInterval 300 和 ClientAliveCountMax 2,让无响应的空闲连接自动断开。

这些参数单个看都不起眼,组合在一起就能把服务器的攻击面缩得很小。比如一台只监听内网、只允许跳板机 IP 访问、只允许特定用户密钥登录的服务器,即使被扫描到也无法直接发起认证请求,安全性直接上了一个台阶。

4.3 面对暴力破解:fail2ban 的配置实战

即使你关闭了密码登录,服务器日志里可能依然会有大量 SSH 连接尝试。这些是扫描器和蠕虫在公网上自动跑的,不代表有人盯上了你,但确实会污染日志、浪费资源。解决这个问题,fail2ban 是应用最广泛的方案。

按系统安装 fail2ban:

sudo apt install fail2ban # 或 sudo yum install fail2ban

然后创建 /etc/fail2ban/jail.local:

[sshd] enabled = true port = 22022 filter = sshd logpath = /var/log/auth.log maxretry = 5 bantime = 3600

如果你用的是 CentOS/RHEL,系统日志路径是 /var/log/secure,要记得把 logpath 改成这个,否则 fail2ban 读不到日志,规则不会生效。port 参数也要改成你自己的 SSH 端口,如果你改过端口但没改这项,jail 还是会去封 22 端口,规则等于没写。

启动后可以用 fail2ban-client status sshd 查看拦截情况,被 ban 的 IP 会列出来。你会在日志里看到大量扫描 IP 被封禁的记录。这里解释一下原理:fail2ban 不是一个“加密”工具,它是一个基于日志的入侵防御工具,当你看到某个 IP 在指定时间内失败认证次数超过 maxretry,就把这个 IP 封掉一段时间。这是应对“网络攻击 SSH 大量连接”最直接有效的手段,配合密码认证关闭,基本能让暴力破解脚本无从下手。

不过 fail2ban 不是万能的。如果攻击者使用大量代理 IP 分散攻击,fail2ban 的效果会打折;如果服务器直接暴露核心业务,还应该配合云平台的安全组、WAF 等外部防护来联动处理。

4.4 进阶防护:证书登录与跳板机

对于团队协作场景,还有一种更高级的密钥管理方式:SSH CA 签名证书。管理员生成一对 CA 密钥,服务器信任这个 CA 的公钥,用户每次用自己的密钥对生成证书请求,CA 签名后证书有效期内就能登录所有信任该 CA 的服务器。这样一来,新员工入职不用一台台机器手动添加公钥,离职后只要撤销该用户的证书或让证书过期即可,管理成本比手工分发公钥低得多。

如果你管理的机器数量多、访问路径复杂,我建议搭建一台跳板机(Bastion Host)。所有开发人员先 SSH 登录跳板机,再从跳板机访问内网机器。内网机器的防火墙只对跳板机 IP 开放端口,相当于把所有访问流量汇聚到一个可控的入口。跳板机上开启审计日志,谁什么时候执行过什么操作都有记录,排查问题的时候非常有价值。这套模式在稍大一点的团队里几乎是标配,虽然配置起来多花点心思,但长远看收益非常大。

5. 常见问题与排查思路记录

5.1 高频问题速查表

下面这张表是我在实际运维中整理的高频问题,绝大部分都能在这里找到答案:

症状可能原因排查方向
Permission denied (publickey,password)密码错误或密码认证被关闭确认密码是否正确,确认能否从控制台进入
服务器拒绝了密码密码认证被禁用或 PAM 配置问题检查 PasswordAuthentication、sshd -t
公钥已添加但仍提示输密码authorized_keys 权限不对检查 .ssh 700、authorized_keys 600
连接超时防火墙/安全组未放行检查云安全组、ufw、firewalld
WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED服务器重装或主机密钥变更确认没有异常后清理 known_hosts
no matching key exchange method found新旧客户端算法不兼容客户端加 KexAlgorithms 或升级 OpenSSH
端口改后连不上新端口未放行检查新端口的防火墙和安全组规则

5.2 日志与调试模式

排查 SSH 问题,日志是第一个要看的。Ubuntu/Debian 的认证日志在 /var/log/auth.log,CentOS/RHEL 在 /var/log/secure。用如下命令跟踪实时日志:

sudo tail -f /var/log/auth.log

你尝试连接一次,日志里会立刻多出几行。凡是“Failed password”或“Connection closed by authenticating user”这类关键字,都能帮你判断认证到底卡在哪一步。

如果服务端日志看不出问题,还可以在客户端开启调试模式:

ssh -vvv user@server_ip

-vvv 会把连接过程的所有细节打出来,包括本地加载了哪个密钥文件、服务器接受了哪种认证方式、最后在哪一步被拒绝。多数时候你能在输出里看到类似“Offering public key: ... Agent admitted failure to sign using the key”这样的信息,它直接告诉你问题出在密钥签名还是权限上。

5.3 不同系统的 SSH 配置差异

我在实际环境中遇到不少非 Linux 设备,配置思路差异比较大,这里列出两个典型的:

华为交换机的 SSH 配置用的是设备命令行的风格,和 Linux 完全不同。基本流程是先全局开启 SSH:

system-view ssh server enable rsa local-key-pair create

然后创建用户并关联 SSH 服务:

ssh user admin ssh user admin authentication-type password ssh user admin service-type stelnet

最后让 VTY 用户界面调用 SSH 协议。这一步经常被忽略,不配置的话即使前面的命令都敲了,登录时依然会被拒绝。网络设备的安全加固思路和服务器是相通的:关闭 Telnet、只保留 SSH、修改默认登录超时时间,这些动作同样能显著降低设备被入侵的风险。

银河麒麟这类基于开源生态的国产系统,SSH 的升级安装也很常见。如果系统是 ARM 架构,安装包需要选对架构的 rpm,直接 yum install 可能拿到的是 x86 包导致装不上。升级 OpenSSH 时更要注意不要中断当前的 SSH 连接,高危操作之前先开一个 screen 或 tmux 会话,或者确认控制台访问可用,避免升级过程中 sshd 起不来导致系统失联。这类系统还有一个特点:PAM 和 SELinux 的配置可能比较特殊,密码认证被拒时优先查 /var/log/secure,很多问题在日志里写得明明白白。

5.4 一次真实的 SSH 故障排查复盘

最后用一个例子串一下排查流程。有一台服务器在公网上,某天突然无法通过密码登录,提示 Permission denied (publickey,password)。

我的排查顺序是这样的:第一次先在另一个网络环境尝试连接,排除本地网络问题;接着查看 /var/log/auth.log,看到 “Disconnected from user xxx ... Received disconnect: 3: ... Authentication failed” 这样的记录,说明服务端确实收到了请求,但认证失败了。

然后检查 sshd_config,发现 PasswordAuthentication 被前任管理员改成了 no,但 authorized_keys 文件里又没有对应的公钥。这就是典型的“安全加固过度”问题。处理方式很简单:通过云控制台的网页终端登录系统,先确认 authorized_keys 里的公钥是正确的,权限正常,再决定是否重新开启密码认证临时入口或直接补齐公钥。这次故障的直接原因就是有人改了配置却没有同步部署密钥,把自己和同事都锁在了外面。

这让我想起一个搜索词:“我走免密登录”——很多人理解的免密登录是输入一条命令就不需要密码了,但“免密”的前提是密钥要能在客户端和服务端之间形成闭环。任何一环断开,免密就会变成“免进”。

6. 写在最后的个人建议

SSH 配置其实不难,难点在于细节。权限不对、端口没放行、配置文件手滑写错、算法版本不兼容,任何一个环节都可能让你在“连不上”的泥潭里反复折腾。我在实际使用中养成了几个习惯,分享给你:

第一,每次改 sshd_config 之前先备份一份 .bak,改完立即执行 sshd -t 检查语法,确认无误后再 reload。第二,生产环境不要直接关闭密码认证,先在另一个全新会话中验证密钥登录可用,再切过去改配置,防止出现把自己锁在门外的尴尬。第三,给自己的私钥设置 passphrase,并且把私钥文件做加密备份,换电脑或硬盘损坏时能救急,而不至于失去所有服务器的访问权。

还有一个实操小技巧:如果服务器数量多,把本机 ~/.ssh/config 纳入版本管理(比如放进自己的私有 Git 仓库),换机器时一条 git clone 就能拉回所有主机配置,省去大量重复配置的时间。当然,私钥本身不要进仓库,存储密钥的文件一定要做好防护。

SSH 这类基础服务,配置好一次之后很少再去动它,但恰恰是这种“低频维护”的地方,出了问题最容易被无视、被拖延。希望这篇文章能帮你把 SSH 从“能连就行”升级到“连得稳、防得住、出事查得清”。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询