这是“AI 时代独立开发者服务器实验”的第二篇实战记录。上篇让一台 68 元的阿里云轻量服务器跑通了 Docker;这一篇继续完成安全底座、后端骨架、域名和备案准备当时仍有四个明确待办:
vm.swappiness 临时改成了 10,重启后却又恢复为 0所以这篇文章的目标不是急着开放网页,而是先把上线前最容易出问题的基础工作补齐
本来计划让Codex直接操作浏览器完成接下来的设置:

可以codex实在太慢了,我还是决定我和codex合作完成:

修改 SSH、重启服务器、调整防火墙,都可能造成真实影响。特别是关闭 SSH 密码登录,如果密钥配置有误,人会直接被锁在服务器外面
因此这次采用的协作方式是:
这会比“一次粘贴几十行命令”慢,但每一步都知道成功条件、失败影响和恢复方法
对不会 Linux 的人来说,这种节奏比记住命令更重要

1. 关闭 SSH 密码之前,先保留两条退路
正式加固前,我同时保留了两种已经验证的连接方式:
私钥只保存在 Mac 的~/.ssh/,没有上传到服务器,也没有交给 Codex
配置写入独立文件:
/etc/ssh/sshd_config.d/00-server-lab.conf核心设置如下:
PubkeyAuthentication yesPasswordAuthentication noKbdInteractiveAuthentication noPermitEmptyPasswords noPermitRootLogin noMaxAuthTries 3LoginGraceTime 30X11Forwarding no这里使用00-前缀,是因为 Ubuntu 会按顺序读取sshd_config.d中的配置,而 OpenSSH 的不少选项采用“先读到的值生效”。云镜像可能已经带有自己的 SSH 配置,文件名排序会直接影响最终结果
写完文件不等于完成。真正的验证顺序是:
sudo sshd -tsudo systemctl reload sshsudo sshd -T | grep -E \ '^(pubkeyauthentication|passwordauthentication|kbdinteractiveauthentication|permitrootlogin) '其中:
sshd -t 检查配置语法reload 平滑加载新配置,不主动中断已有连接sshd -T 查看 OpenSSH 最终实际采用的值,而不是只看某一个文件最后必须从 Mac 新开一个终端,再用私钥登录一次。旧会话还在线,不能证明新连接一定成功
最终复检结果是:
permitrootlogin nopubkeyauthentication yespasswordauthentication nokbdinteractiveauthentication no新密钥会话可以登录,密码认证被拒绝,root 不能直接通过 SSH 登录,Workbench 恢复通道也仍然保留。到这一步,SSH 加固才算闭环

2. 一个真实的坑:把 Mac 命令粘贴到了服务器
测试过程中,我曾在 Workbench 里执行了本应在 Mac 终端运行的命令:
ssh -i ~/.ssh/server_ed25519 admin@服务器地址服务器当然找不到只存在于 Mac 上的私钥,于是提示:
Identity file ... not accessible: No such file or directory随后 SSH 退回密码认证,并连续失败

判断自己在哪个终端,可以先看提示符:
Mac 本地终端:通常以本地用户名开头,以 % 结束服务器终端:通常类似 user@host:~$解决方法不是把私钥复制到服务器,而是回到 Mac 终端执行登录命令。私钥应该始终留在本地可信设备。
这次最有价值的不是 Codex 写出了 SSH 配置,而是它把高风险操作拆成了可以观察的状态:
确认恢复通道 -> 验证独立密钥 -> 备份原配置 -> 写入候选配置 -> 检查语法 -> 平滑加载 -> 新终端密钥登录 -> 验证密码认证已关闭任何一步不符合预期,都停在原地,不继续收紧下一项
不会 Linux 并不意味着可以跳过判断。更现实的方式是让 AI 告诉你:为什么执行、应该返回什么、返回不同结果时该怎么办
SSH 加固完成后,我先创建了内核升级前的系统盘快照,再处理系统更新留下的问题
Ubuntu 服务器如果只有一个固定版本的内核镜像,却没有内核元包,普通软件更新不一定会持续安装后续内核安全版本
最初模拟安装linux-virtual时,会带入约 20 个包,包括头文件、编译和跟踪相关组件。它们不是坏东西,但当前机器只负责运行轻量后端,不需要在生产服务器编译内核模块
继续模拟更精简的linux-image-virtual后,只需要新增 3 个包:
linux-image-6.8.0-136-genericlinux-modules-6.8.0-136-genericlinux-image-virtual下载量约 54MB,磁盘增加约 54.5MB
最终选择linux-image-virtual。它保留了后续内核镜像更新链路,又没有带入当前用不到的开发组件,更适合 2 核 2GB 的个人服务器
这里的重点不是“装得越少越好”,而是让每个长期存在的软件包都有明确用途

swappiness 明明改成 10,为什么重启后又变成 0
这台服务器有 2GB Swap,但阿里云镜像原配置中存在:
vm.swappiness = 0swappiness=0并不是禁用 Swap,但系统会尽量避免使用它。对只有约 2GB 内存、还要运行 Docker 的服务器,我希望保留更温和的缓冲,所以设为10
问题是:运行时执行sysctl -w后显示为10,第一次重启却又回到了0
检查后发现,/etc/sysctl.conf和多个 drop-in 中同时存在这个配置。与其继续增加一个“文件名排得更后”的配置文件,不如先备份,再把所有有效来源统一为同一个值
验证时同时看持久化配置、当前运行值和 Swap 状态:
sudo grep -nE '^[[:space:]]*vm\.swappiness[[:space:]]*=' \ /etc/sysctl.conf /etc/sysctl.d/*.conf 2>/dev/null || truesysctl vm.swappinessswapon --showfree -h维护重启后,结果稳定为:
vm.swappiness = 10/swapfile file 2G
这个问题给我的提醒是:
命令当场成功,只能证明当前运行状态;涉及系统配置,必须再验证一次重启后的状态
安装完成时,新内核文件已经出现在/boot,但服务器当时仍运行旧内核。这是正常的,只有重启后才会切换
重启前,Codex 又让我检查了四件事:
grub-script-check同时保留:

确认这些恢复路径后,我才单独批准维护重启

服务器约两分钟后恢复,运行内核变为:
6.8.0-136-generic随后又验证了内核元包、Swap、SSH、Docker、失败服务、监听端口和是否仍需重启。结果如下:

底座验收通过后,我又创建了一份“首次服务部署前”快照。以后应用部署出问题,可以恢复到这一个干净、可登录、Docker 正常的基线,而不需要从头初始化
服务器底座完成后,才进入真正的应用部分
我目前有两个同一个人主体下的微信产品:
它们都已经上架,目前仍使用第三方接口。第一阶段并不迁移线上业务,也不安装数据库,只做自己的 API 连通测试
后端技术一开始也没有确定。结合服务器配置和个人维护成本,最终先采用:
TypeScript + Node.js 22 + Fastify 5 + Nginx + Docker Compose选择这套组合的原因很朴素:
Python + FastAPI 也很适合个人项目,后续如果增加 AI 或数据处理任务,可以单独增加 Python 服务。Java + Spring Boot 并非不能运行,但对现在的 2 核 2GB 和“只做连通测试”来说,学习与维护成本更高

Codex 在本地项目中创建了共享服务miniapps-api,先实现四个接口:
GET /healthGET /api/v1/pingGET /api/v1/toolbox/pingGET /api/v1/game/ping其中:
/health 用于容器和反向代理健康检查/api/v1/ping 验证通用 API 链路/api/v1/toolbox/ping 对应惜诗工具箱/api/v1/game/ping 对应汉字防线两个产品可以复用同一台服务器、同一个 API 域名和基础设施,但路由从第一天就分开。以后即使共用数据库,权限和数据表也不应该因为“省事”而混在一起
当前本地验证结果是:
这里必须强调:
后端骨架目前只在本地完成,尚未上传或启动到服务器,不能把它描述成线上服务
第一阶段设计的访问链路是:
服务器本机 curl | v127.0.0.1:8080 | vNginx 容器 :8080 | vFastify API 容器 :3000API 容器不映射宿主机端口,Nginx 也只发布到:
127.0.0.1:8080这样第一次部署时,可以先验证:
与此同时,阿里云防火墙不需要开放8080,公网仍然只有 SSH 的22端口

容器还配置了非 root 用户、只读根文件系统、移除 Linux capabilities、进程数限制、CPU/内存限制和 Docker 日志轮转。对个人服务器来说,这些边界比安装一套复杂运维平台更实用
第一阶段只有 ping 和健康检查,数据库解决不了任何真实问题,却会立即带来备份、恢复、升级、密码和数据安全成本
所以当前不安装 MySQL、PostgreSQL 或 Redis
等第一个真实功能明确后,再根据数据类型选择:
数据库不会开放公网端口。即使以后运行在 Docker 中,也只允许应用容器通过内部网络访问,并单独设计备份和恢复验证
域名方面,我原本看到一个.cn域名首年 38 元、正常续费 42 元的方案

后来发现阿里云的一个建站体验活动:购买一个月 15 元的建站产品,可以获得一年域名权益。我的实际首年支出因此变成 15 元,建站系统本身不准备使用

但这种优惠有三个必须核对的地方:
我最终确认建站产品自动续费处于关闭状态,域名以后按正常价格续费,目前显示为 42 元/年
活动规则可能随时变化,这里记录的是一次真实购买经历,不代表所有账号、所有时间都能获得同样价格。购买前必须以结算页、服务协议和正常续费价为准


这次还踩到了一个流程顺序问题
.cn域名注册前,应该先创建个人域名持有人信息模板,并等待模板实名认证通过。否则即使优惠订单已经准备好,也可能因为没有可用模板而无法继续注册
更稳妥的顺序是:
创建个人信息模板 -> 模板实名认证通过 -> 注册域名 -> 域名实名认证完成 -> 等待备案系统同步 -> 提交个人 ICP 备案域名实名认证成功后,阿里云提示至少等待 3 天再进行备案。这个等待不是让域名“再审核一次”,而是让注册局、注册商和备案校验系统同步域名持有人与注册状态
如果过早提交,备案系统可能暂时检索不到域名,或者提示实名信息不一致

等待同步完成后,我通过“万小智·备案助手”提交了个人备案资料。当前进度已经进入:
资料提交完成 -> 阿里云初审(当前) -> 提交管局 -> 工信部短信核验 -> 管局审核

当前只是阿里云初审,不等于备案已经通过。平台显示的预计初审时间也只是服务进度提示,最终仍要以实际审核状态为准


创建个人信息模板 → 模板实名认证通过→ 注册域名→ 域名实名认证完成→ 等待备案系统同步(约3天)→ 提交个人ICP备案
“惜诗工具箱”和“汉字防线”属于同一个人主体,可以共用一个根域名,并使用统一的 API 子域名
但共用服务器不等于微信后台只配置一次
等备案完成、HTTPS 配置成功后,还要分别进入小程序和小游戏的管理后台,把同一个 HTTPS API 地址加入各自的服务器域名配置
正式请求域名通常还需要满足:
因为目前不使用微信支付,这一阶段不需要处理商户平台、支付回调域名和支付证书,范围可以保持很小
当前真实状态如下:

所以这篇完成的不是“正式上线”,而是把上线所需的安全、代码和合规前置条件准备好

83 元只是第一年的已知现金成本。长期预算还必须考虑服务器正常续费、域名续费、对象存储、短信和第三方接口费用
sysctl -w 当场生效,不代表重启后仍然生效ssh.service disabled 不一定是故障,还要检查ssh.socket这一阶段,Codex 帮我完成了:
但它没有替我保管私钥、输入口令、确认高风险操作、决定备案内容或承担审核责任
我越来越确定,AI 管服务器最合理的分工不是“把 root 权限交给它,然后等结果”,而是:
人负责边界、身份、秘密和最终确认;Codex 负责分析、自动化、验证和记录
等阿里云初审、短信核验和管局审核继续推进时,服务器内部部署可以先独立进行
下篇计划完成:
127.0.0.1:8080完成内部连通验证80/443,不开放数据库和容器内部端口到了那一步,这台 68 元服务器才会从“准备好”真正变成“提供服务”
域名注册前,应先创建个人域名持有人信息模板并等待实名认证通过否则即使优惠订单已准备好,也可能因没有可用模板而无法继续注册