BitVPS
VPS 安全加固清单:新服务器上线的头 15 分钟该做什么
安全加固清单

VPS 安全加固清单:新服务器上线的头 15 分钟该做什么

新服务器开机后几秒钟内,就已经能被互联网上的任何地址访问到,而第一次自动化登录尝试,往往在你还没读完凭据邮件时就已经到达。这听起来很吓人,其实大多数时候并不是:这股流量不分青红皂白,手法古老,而且只需一行配置就能彻底挡住。这类机器上真正代价高昂的失败是另一种——把自己锁在一台谁都没法替你解锁的机器外面,因为服务商从一开始就没打算、也没能力核实你是谁。这份清单按照能同时防住这两种情况的顺序,排出了真正重要的八个改动:先留好退路,再关上门,再砌好墙,最后再处理那些你早就忘了还在监听的东西。

永不核查 KYC 忽略 DMCA 无流量日志 60 秒上线

新服务器真正会遭遇什么攻击——又是什么真正会让你失去它

把一个全新的 IPv4 地址接入互联网,不请自来的流量几乎立刻就会出现。这并不是因为有人注意到了你:整个地址空间一直在被持续扫描,一台在 22 端口应答的主机,只是加入了一支早就在排队的队伍。典型的新服务器在第一天,就会记录到几百到几万次不等的 SSH 认证尝试,几乎全部针对 rootadminubuntutest 以及另外十几个显而易见的用户名,密码则来自一份十年来几乎没怎么变过的清单。看起来像是一场攻击,其实更像天气。

这个区分很重要,因为它决定了哪些防护措施值得你花这十五分钟。关掉密码认证,并不会降低这种规模的密码猜测——而是彻底消灭了它。不再有残余风险需要管理,不需要调优,也没有速率需要监控。与此同时,真正会让小型服务器落入他人之手的两件事,其实毫不起眼:一个暴露在公网、运行着某个已公开、且已存在数月漏洞版本的应用,以及一个在别处泄露、却在这里依然有效的凭据。这两者都算不上罕见,也都跟 SSH 监听哪个端口毫无关系。

还有第三种失败模式,而在一个从未问过你是谁的主机上,它才是最常见的一种:你把自己锁在了外面。换成主流服务商,这不过是一张烦人的工单:回答几个问题,证明自己就是账单记录上的那个人,然后有人替你重置访问权限。而在这里,这条路根本不存在——不是服务商不情不愿地选择了不这么做,而是因为这个身份从一开始就没有被收集过。取而代之的,是与账户本身绑定、而非与某个人绑定的访问方式:面板里的紧急 KVM-over-VNC 控制台、保留七天的每小时快照,以及重新启动进入完全不同系统的能力。这些已经足够了——但前提是,你在真正需要之前,就已经用过一次。

所以下面这份清单,是按照后果的轻重排序的,而不是按照流行程度。能够彻底消灭一整类攻击的措施排在最前面,只能降低噪音的措施排在最后,而那个不花一分钱、却能让你保住一整晚的步骤——确认自己依然进得去——排在所有措施之前。

人们担心什么实际是怎么发生的什么能挡住它什么挡不住
SSH 密码猜测自动化扫描,每天成千上万次,用户名来自通用清单PasswordAuthentication no——彻底解决换个非标准端口;用更长的密码
已知有漏洞的服务补丁发布数周后,扫描器比对到你的版本信息自动安全更新,加上重启策略防火墙——如果端口本来就必须开放的话
复用或泄露的凭据从未见过的地址,第一次尝试就登录成功只用密钥认证;每个服务使用独立凭据速率限制——只试一次算不上突发流量
暴露的内部服务数据库、缓存或指标接口绑定在了通配地址上绑定到回环地址;两个协议族都默认拒绝入站以为它是私有的,只因为你从未把链接发出去过
失去自己的访问权限你做了加固、退出登录,结果发现改错了保留一个开着的第二会话、一份快照、一个测试过的控制台自信

先留好退路,再关上门

下面这个顺序最容易把人困住,而其中每一步单独看都没有错:编辑 SSH 配置、换端口、关掉密码登录、开启防火墙、重载守护进程、关掉终端。问题不出在任何一个单独的改动上,而在于:你正在输入的这个会话,是唯一能证明这些改动生效的证据,而你却在验证之前就把它扔掉了。

让整份清单变得安全的规则,只有一句话:让能正常工作的会话保持打开,在第二个会话里验证改动。打开一个新终端,重新连接,运行 id,用 sudo 提权。如果成功了,说明改动是真的生效了,第一个会话就变得可以丢弃;如果失败了,你手上依然有一个 shell 可以用来撤销它。这条规则适用于防火墙、sshd、新用户的密钥,以及任何可能在门口把你拒之门外的东西。

在做这些之前,先建立一条带外通道。在 BitVPS 面板里,每一行服务器都带有一个控制台按钮,可以打开一个针对该虚拟机显示画面的 KVM-over-VNC 会话。它不是网络上的一个 shell,而是这台机器本身的屏幕和键盘——所以即便 sshd 起不来、防火墙丢弃了每一个入站包、网络配置出了错,它依然能用。现在就在一台你还没弄坏的服务器上打开它一次,确认能看到登录提示。这只需要三十秒,却是一次失误和一次故障之间的全部差别。

也打一份快照。每个套餐都自带每小时快照、保留七天,恢复只需要大约三十秒——这能把“我粘错了一条 nft 规则”从一整晚的抢救,变成一次回滚。而对于那些你确实没有十足把握的改动,死人开关只需要一行:systemd-run --on-active=10min /usr/sbin/ufw --force disable 会启动一个定时器,十分钟后自动撤销防火墙改动,除非你取消它。如果新规则没问题,你就取消它;如果它们把你锁在了外面,机器会在你无需在场的情况下自己把门重新打开。

SSH:真正起作用的四条指令——以及会悄悄覆盖它们的那个文件

在自己的机器上生成密钥,永远不要在服务器上生成:ssh-keygen -t ed25519 -C "laptop-2026"。到了 2026 年,ed25519 是正确的默认选择——密钥短、验证快、没有容易选错的参数,而且过去十年构建的每一个 OpenSSH 都支持它。4096 位的 RSA 并非不安全,只是更大、更慢,却没有换来任何好处。给私钥设置一个口令;有了 ssh-agent,每个会话只需要输入一次,而它正是一台被盗笔记本电脑和你名下每一台服务器之间,唯一的那道屏障。

ssh-copy-id 把公钥那一半拷贝上去,并且在你禁用任何东西之前,先确认能用它登录。权限很重要,OpenSSH 在设计上对此非常严格:~/.ssh 要是 700authorized_keys 要是 600,而且都要归属于该用户。一把悄无声息就是不好用的密钥,几乎总是权限问题,在前台运行 sshd -e -d 会用一行输出告诉你答案。

然后 /etc/ssh/sshd_config 里的四条指令,基本上就完成了全部工作。这个文件里其余的一切,都只是偏好设置。

指令设成什么它的作用设错了会出什么问题
PasswordAuthenticationno彻底移除密码这种登录方式——整股暴力破解流量不再是变慢,而是彻底不可能如果你的密钥其实从未真正装上,你就会被锁在外面。先在第二个会话里测试
KbdInteractiveAuthenticationno关上通向同一个房间的第二扇门——即便设置了前一条指令,PAM 的键盘交互认证路径依然可能接受密码会破坏依赖 PAM 的正规双因素设置。只有在你确实用到它时才保留开启
PermitRootLoginprohibit-passwordroot 仍可用密钥登录(便于自动化),但绝不能用密码登录no 更严格,也完全可行——前提是你的 sudo 用户能正常工作。先验证这一点
PubkeyAuthenticationyes这是你要保留的登录方式。这里明确写出比依赖默认值更好不会出问题,但明确写出的意义在于:以后修改配置时,不会被悄悄漏掉
AllowUsers / AllowGroups你的用户名或用户组可选,但却是价值最高的一条可选配置——守护进程会在认证之前就拒绝所有其他账户打错一个字就会把所有人都拒之门外。放在最后设置,并且在第二个会话里操作

接下来是连有经验的人都会栽跟头的部分。在 Debian 12、Ubuntu 22.04 及更新版本上,/etc/ssh/sshd_config 一开头就是 Include /etc/ssh/sshd_config.d/*.conf,而 OpenSSH 对每个关键字只采用它拿到的第一个值。云镜像通常会在那个目录里预置一些插入文件——常见的一种就设置了 PasswordAuthentication yes——而由于这条 include 位于文件最顶端,这个插入文件会盖过你在两百行之后小心翼翼改好的那一行。文件上写的是一回事,守护进程实际做的是另一回事。

解决办法是永远不要相信这个文件本身。直接问守护进程它最终解析出了什么:sshd -T | grep -Ei 'passwordauth|permitrootlogin|kbdinteractive|pubkeyauth' 会打印出所有 include、match 块和默认值都应用完之后的有效配置。这一条命令,比这一节剩下的内容都更有价值。搭配 sshd -t 一起用,它会校验语法,出错时以非零状态退出;然后才执行 systemctl reload ssh——重载不会打断已有会话,所以哪怕重载的结果是错的,你当前的 shell 也依然活着。

至于把 SSH 挪出 22 端口:这大概能去掉你日志量的 95%,却去不掉你风险的一分一毫。任何值得担心的扫描器都会扫遍全部 65,535 个端口,而那些不这么做的扫描器,本来也不可能突破只认密钥的认证。如果日志噪音让你心烦,或者它能让某条监控告警安静下来,那就换个端口——但不要把它当成一项安全措施来记录;而且如果你真要换,就换到 1024 以下,因为只有特权进程才能绑定这些端口,这样一来,即便守护进程哪天停了,也不会有非特权进程能悄悄占用这个端口。

防火墙:默认拒绝入站,以及所有人都会忘记的另一半

单一用途服务器上的防火墙只有一项工作:让可访问端口的集合,正好等于你能说出理由的端口集合。默认拒绝入站,放行出站,放行已建立和相关的连接返回,然后只开放你实际在用的那两三样东西。如果你用的是 Debian 或 Ubuntu,ufw 四条命令就能搞定,而且够用;如果你更想自己掌控一份可读的规则文件,nftables 是现代内核内置的答案,也是 ufw 在底层实际写入的对象。

顺序只在一个地方真正要紧:在启用策略之前,先放行你的 SSH 端口。先 ufw allow 22/tcp,再 ufw default deny incoming,最后才 ufw enable。顺序反了,启用这一步就会直接掐断你正在输入的这个会话——如果你做过上一节的内容、手上还有控制台,这还能挺过去,否则这就是提前结束一整晚的经典方式。

被遗忘的另一半是 IPv6。这里的每台服务器都自带一段可路由的 /64,而服务默认会同时绑定两个协议族。单独用 iptables 写出的规则集,只管得到 IPv4,对 IPv6 完全不置一词——于是你精心用防火墙保护起来的数据库,依然能通过另一个协议在整个互联网上被访问到。ufw 能同时处理两者,只要 IPV6=yes/etc/default/ufw 里被设置好(在当前版本上这是默认值——去检查一下,别想当然);nftables 则从结构上就避开了这个问题:一份 table inet 规则集,用同一套规则同时覆盖两个协议族。如果你只想手写一行防火墙配置,就写这一行。

然后还有 Docker——与其说它绕过了你的防火墙,不如说它是从防火墙下方直接冒出来的。用 -p 5432:5432 发布一个端口,会往 nat 表里插入 DNAT 规则,而这些规则ufw 管理的过滤链之前就已经被评估过。结果就是:容器能从互联网访问到,而 ufw status 却显示该端口是关闭的——每年都有人在一个自以为处在拒绝一切策略之后的容器里跑数据库,为此感到意外。改成只发布到回环地址——-p 127.0.0.1:5432:5432——或者把容器放进一个内部网络,让另一个容器通过名字去访问它。

你以为的情况实际情况怎么用一条命令验证
“防火墙会拒绝我没有放行的一切”只有你写的是纯 IPv4 规则时,这句话才只对 IPv4 成立ip6tables -L -nnft list ruleset
“数据库没有暴露,它在容器里”已发布的端口无论过滤策略如何都能被访问到docker ps --format '{{.Ports}}'
“那个端口上什么都没监听”你上次查看之后,有什么东西重启并重新绑定了它ss -tulpn
“端口是关闭的,我扫描过了”从机器自身发起的扫描,测的是回环地址,不是互联网从第二台主机扫描,或者从你自己的笔记本电脑扫描

到底什么在监听——一条命令终结所有猜测

一条命令就能回答关于服务器暴露面的最有用的那个问题:ss -tulpn。它会列出每一个正在监听的 TCP 和 UDP 套接字,以及拥有它的进程,而真正要紧的那一列是本地地址。127.0.0.1:5432 表示只有这台机器自己能连上。0.0.0.0:5432 表示任何能到达这台机器的地址都能连上,唯一的限制只剩防火墙。[::]:5432 说的是同一件事,只不过换成了 IPv6,也是人们最容易一扫而过的那一行。

反复出问题的,永远是那几个熟面孔。Redis,在其历史上的大多数时间里都绑定范围过宽,而且默认不带认证。PostgreSQL 和 MySQL,配置文件被改成“只接受来自应用的连接”,结果那个应用后来搬走了。MongoDB 和 Elasticsearch,两者加起来,正是因为这个原因贡献了整整十年的数据泄露头条。Memcached,它的 UDP 监听器一度是热门的带宽放大器。Prometheus 的 node_exporter,只要有人问,它就会乐呵呵地把主机内部情况和盘托出。2375 端口上的 Docker API,本质上是套着 REST 接口外衣的远程代码执行。还有那个十一个月前有人在 tmux 窗格里“就开一小会儿”的开发服务器。

规则很简单,也不需要什么判断力:如果一个服务不需要能从互联网访问到,就把它绑定到回环地址。你自己需要用到它的场景照样都能用——ssh -L 5432:127.0.0.1:5432 user@server 会给你一个本地端口,通过你已经信任的那条连接建立隧道;如果有好几台机器都需要访问,一个私有的 WireGuard 接口可以给它们一段永远碰不到公共互联网的地址范围。两种办法都不花一分钱,而且都是把服务彻底移出攻击面,而不是去防守它。

要从外部验证,而不是从内部。你自己的机器报告某个端口被过滤了,这只是关于你自己回环协议栈的一个说法;而第二台机器连不上,才是证据。从你的笔记本电脑,或者从另一台服务器发起连接,检查那些你预期开放的端口,再检查一个你预期关闭的端口——一个只能通过、不可能失败的测试,根本不算测试。

服务典型默认状态暴露之后会交出什么解决办法
Redis绑定范围过宽,历史上默认无密码完整读写权限,外加一条广为人知的、以 redis 用户身份写文件的路径绑定回环地址;设置 requirepass;保持 protected-mode 开启
PostgreSQL / MySQL默认回环地址,直到有人改动它只要凭据较弱或被复用,你的整个数据集就会拱手让人绑定回环地址;通过 SSH 或 WireGuard 访问
Docker API默认关闭——除非某篇教程教你打开了它主机上等同于 root 权限的代码执行能力永远不要暴露它;改用 SSH context
node_exporter / metrics所有接口,9100 端口主机名、挂载点、版本信息、正在运行的进程绑定回环地址;通过私有接口抓取
邮件提交、Web 管理面板出于需要,必须监听所有接口一个密钥保护不到的密码猜测暴露面这才是真正该用速率限制的地方

fail2ban 与 CrowdSec:密码认证关掉之后,它们还值多少

这是大多数清单都放在靠前位置、也是大多数运维人员高估了其价值的一项,所以值得说清楚。设置了 PasswordAuthentication no 之后,密码猜测不可能成功——不是难得成功,也不是很难成功,而是完全不可能成功。五次失败后封禁一个地址,并不会让一件本就不可能的事情变得更不可能。fail2ban 在 SSH 这一层给你带来的,是更安静的 auth.log、连接建立时略微少一点的 CPU 开销,以及更容易看懂的监控图表。这些都是实实在在的好处,但它们属于运维卫生,不是安全措施——把它们归错了类,结果就是一份加固得很漂亮的日志文件,配上一个从未打过补丁的应用。

这类工具真正派上用场的地方,是每一处密码依然存在的层面:Web 应用的登录表单、邮件提交端口、Nextcloud 或 WordPress 的管理后台、IMAP 服务器。这些地方都没法换成公钥认证,猜测依然可能得逞,速率限制才是真正的防线。CrowdSec 在此之上又加了一层共享信誉源,这在 HTTP 层相当有用——别人已经见识过的作恶地址,你也能因此受益;而一旦密钥认证成为强制项,它在 SSH 层就几乎没什么意义了。

有两个自坑陷阱值得点名。第一个是把自己也封了,通常发生在凌晨三点测试什么东西的时候;把 ignoreip 设置成包含你自己的地址段,同时记住:一个共享地址或运营商级 NAT 地址,意味着封禁“一个攻击者”,也可能顺带封掉几百个毫无关系的人。第二个是这个工具必须能读到它要过滤的日志:在那些只往 systemd journal 写日志的发行版上,默认的文件后端会悄悄盯着一个早已不再收到任何内容的文件,而对应的 jail 就那样看起来很健康地待在那儿,实际什么都没做。

老实说,建议是这样:如果日志噪音让你心烦,就装上它;把它配置在真正用得上的应用层,而不要把它的作用重复计算两遍。如果你想要同样的安静、却不想维护额外的东西,SSH 守护进程里的 MaxStartupsLoginGraceTime,加上只认密钥的认证,已经能达到大部分效果,而且不会把你锁在自己的机器外面。

打补丁:枯燥,却能防住大多数真实入侵的措施

随便找一篇涉及小型公网服务器的事件复盘来读,模式总是一样的:漏洞有补丁,补丁已经发布了几周甚至几个月,却没有人打上它。CISA 的已知在野利用漏洞目录在这里值得一读,恰恰因为它列的不是理论上的严重程度,而是眼下正在被实际利用的东西,而其中大多数都早已有了公开的修复方案。自动安全更新,也因此成了这整篇文章里投入产出比最高的十五秒。

在 Debian 和 Ubuntu 上,安装 unattended-upgrades,用 dpkg-reconfigure -plow unattended-upgrades 启用它,并确认 /etc/apt/apt.conf.d/50unattended-upgrades 里选中的正是安全源。用 unattended-upgrades --dry-run --debug 验证它确实在做事——这条命令会准确打印出它会拉取哪些包、为什么拉取,Debian wiki 页面是权威参考。在 Rocky 和 Alma 上,dnf-automatic 配合 apply_updates = yes 并启用定时器,做的是同一件事。只限定在安全仓库,是刻意为之:这是那种几乎从不会搞坏任何东西的配置,也正因如此,才是人们会一直让它开着的那种自动化。

然后要明确地对重启做出决定,因为这项防护通常就是在这里出现漏洞的。安装一个新的内核软件包,并不会替换掉你正在运行的那个内核;替换一个共享库,也不会重启那些早已把旧版本映射进内存的守护进程。Debian 和 Ubuntu 上的 needrestart 会准确列出哪些服务还占着已删除的库;内核更新需要重启,没有商量余地,而实时打补丁是一项付费功能,不会出现在一台 8.50 美元的实例上。要么设置 Unattended-Upgrade::Automatic-Reboot,配上一个安静的 Automatic-Reboot-Time,要么把每月一次的重启写进你的日历,把它当作例行维护,而不是一次事故。

最后,要清楚自动更新覆盖不到什么,因为人们往往就是在这个缺口里出事的。任何在包管理器之外安装的东西,对它来说都是不可见的:从 GitHub release 拉下来的一个二进制文件,通过 pipnpmcargo 装的语言运行时依赖,Web 应用里的一个主题或插件,最重要的是,容器镜像。一台报告“零待处理更新”的主机,完全可能正在运行一个两年前构建的基础镜像,而机器上没有任何东西会告诉你这一点。如果你在跑容器,按计划重新构建、重新拉取,本身就是打补丁的一部分,而不是一项独立的工作。

系统机制覆盖范围悄悄漏掉的部分
Debian / Ubuntuunattended-upgrades,仅限安全源发行版软件包,每晚应用正在运行的内核和已加载的库,直到重启前都不会更新
Rocky / Almadnf-automatic,配合 apply_updates = yes效果相同,通过 systemd 定时器执行同样存在重启缺口;用 dnf needs-restarting 检查
容器默认什么都不做什么都不覆盖一切——镜像在构建时就已经被冻结
语言依赖默认什么都不做什么都不覆盖你应用真正的攻击面

用户、sudo,以及那些会悄悄重新打开的门

创建一个普通用户,把它加入 sudowheel 组,把密钥装在这个用户下,不要再用 root 登录。原因并不是带密钥的 root 本身有多危险——而是它是一个单因素账户,用户名人尽皆知,也没有任何记录能说明到底是哪个人在用它。一个具名账户加上 sudo,就有了审计轨迹,可以让多个运维人员共用而不必共享同一份凭据,也能让日后“到底是谁执行了那条命令”这个问题有答案可查。但不要把 NOPASSWD 授予它做任何事的权限,然后又把这个账户拿去复用给某个 Web 服务。

服务同样也不应该以 root 身份运行,而 systemd 让这件事的低成本版本几乎不花什么代价。用 systemctl edit <unit> 创建一个插入配置,写上 NoNewPrivileges=yesProtectSystem=strictProtectHome=yesPrivateTmp=yes,就能把一个被攻陷的守护进程限制在一个只读的文件系统视图里,没有任何提权路径——短短四行,记录在 systemd.exec 文档里,却能把许多应用层漏洞,从“拿下整台主机的 root”,变成“日志里的一条错误”。systemd-analyze security 会为机器上的每个 unit 打分,告诉你哪些值得花那五分钟。

authorized_keys 保持干净可信。每台设备一把密钥,每把密钥都带一条注明设备身份的注释,认不出来的一律删掉。任何部署工具运行之后,都要重新读一遍这个文件,因为 cloud-init、镜像构建流程和控制面板都会往里面追加自己的条目——一把你自己没添加过的密钥,就是一把你没法撤销的密钥。账户也是同样的道理:镜像自带的默认用户,密码是脚本设置的、之后再没用过,就是一个你没有在盯着的登录入口。

这份清单里最隐蔽的一项,是 agent 转发。ForwardAgent yes 会让远程主机上的进程,在你连接期间使用你本地的私钥——这正是它的设计初衷,也正因如此,一台你并不完全掌控的机器上的 root,就能借用你的密钥,去访问所有信任这把密钥的其他服务器。改用 ProxyJumpssh -J bastion target):它通过隧道转发连接,给你同样的可达性,而你的密钥永远不会在中间那台主机上变得可用。如果你必须转发 agent,就在 ~/.ssh/config 里按主机逐台开启,永远不要全局开启。

备份:把糟糕的一天,变成糟糕的一下午

以上所有措施,降低的都是糟糕一天发生的概率。而备份决定的,是那个糟糕的一天要付出多大代价——在一家刻意做到无法识别你身份的服务商这里,备份的分量比平时更重:不存在什么支持渠道能凭别人的副本替你重建机器,不存在什么账户找回流程能悄悄把恢复权限交到你手上,也没有哪条账单记录上挂着一个电话号码。你手上有什么,就只有你自己留下的那些。

真正管用的方案并不激动人心。用 resticborg,在服务器上就完成加密,让数据在跨越网络之前就已经不可读,再推送到第二台机器——理想情况下还要换一个司法管辖区,而当同一个面板就提供冰岛、荷兰、罗马尼亚和瑞士时,这不过是勾选一个选项,算不上一个额外项目。每个套餐自带的每小时快照,作为第一道防线确实很不错,能完美覆盖“一小时前我改坏了配置”这种情况。但它覆盖不到你五周前删掉的某个文件,而且它和它要保护的对象,活在同一套基础设施上。快照和备份解决的是不同的问题;两个都要做。

让备份仓库仅追加。这个细节,正是备份和副本的分水岭:一台被攻陷的服务器,手里握着通往自己备份目标的凭据,而任何能删除数据的东西,都会被指使去删除数据。borg serve --append-only 和 restic 的 rest-server --append-only,都会在接收端强制执行这一点,让机器可以写入新快照,却无法删除旧快照。清理旧数据的操作要放在别处执行,按计划进行,使用一份服务器从未见过的凭据。

最后两件事,人们通常都会跳过。仓库口令绝不能只存在于它所保护的那台机器上——把它写下来,存进别处的一个密码管理器,把它当成一段种子助记词来对待,因为它实际上就是。另外,要恢复一次。一份从未恢复过的备份,只是一个假设;发现某条排除规则悄悄漏掉了数据库转储,通常都是在你输不起的那次真实恢复中才会发现。开一台 Starter 实例,把备份恢复进去,检查数据,然后销毁它。这只花 8.50 美元和一个小时,也是让这篇文章余下的内容,从“第一次实战”变成“一次演练”的唯一办法。

加固保护不了你什么

它保护不了磁盘,如果磁盘落在了别人手里。这篇文章里的每一项措施,前提都是操作系统正在运行、并且在强制执行自己的规则;一台已关机、存储被整体镜像的机器,这些措施统统用不上。那是另一个问题,需要另一个答案——参见全盘加密、远程解锁,以及查封实际能拿到什么——老实说,加固和加密防的是两类互不重叠的威胁,做好其中一个,并不会让你在另一个上也拿到部分分数。

它也修不好你运行的应用。一台服务器可以通过这里的每一项检查,却依然会因为一个过时的论坛程序、一个没有身份验证的管理接口、一个直接对用户输入求值的模板引擎,或者一个被提交进公开仓库的令牌,而彻底失守。主机层面的加固,能缩小应用被攻陷之后的波及范围——上文的 systemd 限制正是为此而做——但它审计不了你的代码,而小型服务器最常见的失守方式,依然是运维人员自己装上去的某个东西。

它也改变不了谁知道这台机器是你租的。一台加固得无懈可击的机器,如果是用经过身份核验的交易所账户付款、用私人邮箱下的单,那它就是一台写着你名字的、加固得无懈可击的机器。安全这一层和身份这一层是相互独立的,而后者是在结账时决定的,不是在 shell 里决定的——加密货币托管到底有多可追溯讲清楚了这些关联究竟是在哪里形成的。

它对流量分析也无能为力。一个能观察到你连接的旁观者,能摸清这些连接的形状——时机、流量大小、谁在跟谁通信——不管两端配置得多么周全,都是如此。加密隐藏的是内容,不是对话本身,再怎么调优 sshd 也改变不了这一点。

这份清单,按执行顺序排列

一共八个步骤。前四个就是标题里说的那十五分钟,能彻底消灭整类整类的风险;后四个耗时更长,却是“一台你能守住的服务器”和“一台你只是配置过的服务器”之间的分水岭。每一行都写明了你怎么知道它生效了,因为一个你没有验证过的加固步骤,等于一个你没有做过的加固步骤。

#步骤怎么知道它生效了
1打开面板控制台,确认能看到登录提示,打一份快照你在一台还没出问题的服务器上看到了登录提示
2创建一个非 root 用户,装上你的 ed25519 密钥,测试 sudo用第二个终端以该用户身份登录并成功提权
3关闭密码认证和键盘交互认证sshd -T 报告 passwordauthentication no
4建一道覆盖 IPv4 和 IPv6、默认拒绝入站的防火墙nft list ruleset 显示两个协议族都已覆盖;从另一台主机扫描的结果也一致
5审计监听中的套接字,把内部服务重新绑定到回环地址ss -tulpn 里没有任何一个你说不清理由的通配绑定
6启用自动安全更新,并定好重启策略unattended-upgrades --dry-run --debug 选中的是安全源
7用 systemd 限制服务权限,清理 authorized_keyssystemd-analyze security 的评分变好;每一把密钥都有名字
8建一份加密、仅追加、异地存放的备份,并做过一次恢复你恢复到一台一次性实例上,数据确实都在

这里没有一项是花哨的东西,而这正是重点。在小型服务器上真正要紧的措施,都是那些能彻底消灭一整类问题、而不是去管理它的措施:没有密码、没有意外的监听、没有未打补丁的软件包、数据不只有一份副本。除此之外的一切都只是偏好,而一旦那四项承重的改动都已到位并验证过,偏好就变得很廉价了。

如果你现在正在开通这台机器,而不是提前通读全文,那么整套流程完全可以在第一个会话里舒舒服服地做完:部署一台实例,先打开一次控制台确认它能用,然后在安装任何东西之前,按顺序执行这八个步骤。一台在装上任何服务之前就已经加固好的服务器,不需要绕开任何历史包袱——这是一台新机器唯一的优势,而它大概只能维持一天。

快速解答

常见问题

要不要把 SSH 挪出 22 端口?
它能去掉你大部分的日志噪音,却去不掉你的任何风险。值得关心的扫描器会扫遍全部端口范围,而那些只试探 22 端口的扫描器,本来也不可能突破只认密钥的认证。如果噪音让你心烦,或者换端口能让某条监控告警安静下来,那就换——只是不要把它记成一项安全措施,并且记得在重载守护进程之前,先在防火墙里放行新端口。
如果我只允许 SSH 密钥登录,还值得装 fail2ban 吗?
在 SSH 这一层,它换来的是安静,不是安全:设置了 PasswordAuthentication no 之后,猜测不可能成功,对一件本就不可能发生的登录做速率限制,什么都改变不了。它真正有价值的地方,是那些密码依然存在的层面——Web 管理面板、邮件提交、IMAP 服务器。把它装在那些地方,把 ignoreip 设置成包含你自己的地址段,避免把自己也封了,并确认它读取的日志确实还在持续收到内容。
我把自己锁在服务器外面了——到底还能做什么?
点开面板里服务器那一行的控制台按钮。它是一个针对机器自身显示画面的 KVM-over-VNC 会话,所以即便 sshd 起不来、防火墙丢弃了每一个数据包,它依然能用——从那里登录进去,撤销那次改动。如果系统彻底启动不了,就恢复一份每小时快照(保留七天,大约三十秒即可完成)。我们做不到的是验证“你就是你”并替你重置访问权限:注册时就没有收集过身份信息,所以与账户绑定的访问方式,是唯一存在的恢复路径。
如果什么都没在监听,还需要防火墙吗?
需要,因为“什么都没在监听”这句话,说的只是这一刻的状态。一次软件包更新可能启动一个守护进程,一个容器可能发布一个端口,一个同事可能测试点什么然后忘了关掉。默认拒绝策略,能让这些情况统统变成无关紧要的小事,而不是你日后才发现的一次暴露。它只需要四条命令,也是这篇文章里唯一一项能保护你、防住你自己未来改动的措施。
PermitRootLogin:该用 prohibit-password 还是 no?
prohibit-password 是合理的默认选择——root 依然可以用密钥认证,让自动化和紧急访问都还能用,但绝不能用密码认证。no 更严格;如果你已经验证过 sudo 用户能在第二个会话里正常工作,那么它也同样正确——这一步验证,应该在你设置任何一个选项之前就先做好。真正重要的那一半是:root 绝不能接受密码;至于它是否接受密钥,只是一项运维层面的偏好。
自动安全更新会不会弄坏我的服务器?
只要限定在安全源,这种情况很少见——这个仓库存在的意义,正是只发布最小化的修复、不引入行为变化,也正因如此,人们才会让它一开就是好几年。更大的风险其实是反过来的:某个运维人员因为一次糟糕的经历就关掉了自动化,结果一落后就是六个月。如果你托管的东西比较脆弱,就启用自动下载、手动应用,并把“应用”这一步写进你的日历,而不是仅仅放在你的打算里。
为什么 ufw 显示端口已关闭,我的 Docker 容器却还能被访问到?
因为发布一个端口,会往 nat 表里写入 DNAT 规则,而内核评估这些规则的时机,早于 ufw 管理的那些过滤链——数据包在拒绝策略还没来得及看到它之前,就已经被重定向到了你的容器。改成用 -p 127.0.0.1:5432:5432 只发布到回环地址,再通过 SSH 隧道访问,或者把容器放进一个内部 Docker 网络,让它压根不发布任何端口。查看 ufw status 永远发现不了这个问题;ss -tulpn,以及从另一台机器发起的扫描,才能发现。
这些事情要多久重新做一次?
配置类的步骤都只需要做一次。有三件事需要定期回顾:每次安装可能带来监听的东西之后,查一遍 ss -tulpn;每次设备易手时,查一遍 authorized_keys;大约每年做两次恢复测试。除此之外的一切,要么是自动的,要么就不会变。日历上每六个月一条“查监听、查密钥、做恢复”的提醒,就覆盖了这篇文章带来的全部持续性负担。
应用指南

本指南适用的工作负载

每张卡片打开包含规格建议和系统管理员 FAQ 的工作负载专属页面。

继续阅读

其他指南

延伸阅读,从本指南停止处继续。

读够了吗? 60 秒内部署

无需邮箱验证,无需身份证,无需账户。选择套餐,以任意加密货币支付,获得 root 权限。