前几天帮朋友排查一台 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 sshRHEL/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 sshsshd -t 这一步必须养成习惯它能把配置文件的语法错误提前暴露出来避免 reload 之后把自己锁在门外。下面这几个参数是配置时的重点参数默认值说明Port22监听端口改成非默认端口可减少扫描ListenAddress0.0.0.0限制监听地址比如只监听内网 IPPermitRootLoginprohibit-password是否允许 root 登录建议禁止密码登录PasswordAuthenticationyes是否启用密码认证加固阶段改为 noPubkeyAuthenticationyes是否启用密钥认证保持 yesMaxAuthTries6单次连接最大认证尝试次数建议改小ClientAliveInterval0服务端向客户端发送保活包间隔ClientAliveCountMax3客户端无响应多少次后断开Port 改成非 22 只是个“降低暴露面”的手段它不是安全的核心但确实能帮你躲掉一大波无脑扫描脚本。改成 22022 或者 50022 这类端口后你的 auth.log 里恶意尝试会少很多。注意改完端口后云安全组、防火墙规则要同步改否则下一次连接就会超时。2.3 防火墙与云安全组的联动配置刚才说的防火强放行这里展开讲讲。如果你用 ufwsudo ufw allow 22/tcp sudo ufw enable sudo ufw status如果用 firewalldsudo firewall-cmd --permanent --add-port22/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这时候才需要退回 RSAssh-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 userserver_ip这条命令会自动把公钥追加到服务器上对应用户的 ~/.ssh/authorized_keys 文件并且处理好权限。如果你因为某些原因没法用 ssh-copy-id手动操作也完全可以在本机执行 cat ~/.ssh/id_ed25519.pub 复制输出内容然后在服务器上编辑 ~/.ssh/authorized_keys把内容粘贴进去。这里有一个极其关键的细节权限必须严格。.ssh 目录权限应该是 700authorized_keys 文件权限应该是 600。权限只要多一个“组可读”SSH 服务就会认为文件不安全直接忽略这个文件表现为“密钥没生效”“还是让输密码”。chmod 700 ~/.ssh chmod 600 ~/.ssh/authorized_keys如果你登录的是 root还要检查 root 用户的家目录本身不能被组或其他用户写否则同样会被拒绝。这个权限问题是我见过最多的 SSH 配置坑没有之一。3.3 用 config 文件管理多台服务器当你管理的服务器多了之后每次输入 ssh userip -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密钥认证不止用于登录 shellGit 的远程仓库操作也是同一套机制。GitHub、GitLab 都支持把公钥添加到账户里添加之后你 clone、push 就不用反复输入账号密码了。GitLab 里添加密钥的位置在 Preferences - SSH KeysGitHub 在 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_configPermitRootLogin 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 是应用最广泛的方案。按系统安装 fail2bansudo 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、firewalldWARNING: REMOTE HOST IDENTIFICATION HAS CHANGED服务器重装或主机密钥变更确认没有异常后清理 known_hostsno matching key exchange method found新旧客户端算法不兼容客户端加 KexAlgorithms 或升级 OpenSSH端口改后连不上新端口未放行检查新端口的防火墙和安全组规则5.2 日志与调试模式排查 SSH 问题日志是第一个要看的。Ubuntu/Debian 的认证日志在 /var/log/auth.logCentOS/RHEL 在 /var/log/secure。用如下命令跟踪实时日志sudo tail -f /var/log/auth.log你尝试连接一次日志里会立刻多出几行。凡是“Failed password”或“Connection closed by authenticating user”这类关键字都能帮你判断认证到底卡在哪一步。如果服务端日志看不出问题还可以在客户端开启调试模式ssh -vvv userserver_ip-vvv 会把连接过程的所有细节打出来包括本地加载了哪个密钥文件、服务器接受了哪种认证方式、最后在哪一步被拒绝。多数时候你能在输出里看到类似“Offering public key: ... Agent admitted failure to sign using the key”这样的信息它直接告诉你问题出在密钥签名还是权限上。5.3 不同系统的 SSH 配置差异我在实际环境中遇到不少非 Linux 设备配置思路差异比较大这里列出两个典型的华为交换机的 SSH 配置用的是设备命令行的风格和 Linux 完全不同。基本流程是先全局开启 SSHsystem-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 从“能连就行”升级到“连得稳、防得住、出事查得清”。