一台全新轻量应用服务器,从购买到 HTTPS 上线的完整过程。
这个页面本身,就是整个过程的产物。它运行在一台刚创建不到一小时的服务器上,证书由 Caddy 自动向 Let's Encrypt 申请。下面记录每一步实际做了什么,包括踩到的坑。
域名的根站和另外三个子域(test / pi / dsh)已经跑在一台位于马来西亚新山的实例上。本次需要一个新的独立环境,用于验证「新机器 + 新子域 + 自动证书」这条链路能否一次跑通。
目标很明确:不做业务,只做一个能访问的页面,把过程写清楚。
选型原则是「够用就好」,因为这台机器只承担一个静态页面:
选择最小规格是刻意的。静态页面的资源消耗几乎只来自 Caddy 进程本身,实测内存占用在 10 MB 量级。花更多钱买规格,对这个场景没有意义。
续费方式设为手动,是为了避免忘记而产生不必要的长期支出——这台机器本身就是一次验证,验证完就该退掉。
新实例默认已放通 22(SSH)、80(HTTP)和 ICMP。需要额外补一条 443(HTTPS):
添加规则:
协议 TCP
端口 443
来源 0.0.0.0/0
动作 允许
这里有个容易忽略的细节:80 端口不能关。Caddy 申请 Let's Encrypt 证书时,ACME 协议需要通过 80 端口完成 HTTP-01 挑战。只开 443 会导致证书签发失败,站点直接起不来。
没有选 Nginx,是因为 Caddy 的自动 HTTPS 是内建能力——声明域名后它自己去申请、续期、OCSP stapling 全部自动完成,不需要额外配置 certbot,也不需要写定时任务。对于「就想快速上线一个 HTTPS 站点」这个需求,它是最短路径。
通过官方 apt 源安装,方便后续随系统一起升级:
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' \
| sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' \
| sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update
sudo apt install caddy
装完版本是 v2.11.7,systemd 服务已自动注册并设为开机自启。
open /var/log/caddy/xxx.log: permission denied。原因是 Caddy 服务以 caddy 用户身份运行,而日志目录属于 root。修复方式是建目录并改归属:chown -R caddy:caddy /var/log/caddy。这类权限问题在 systemd 服务里很常见——手动跑没问题,切成服务流用户就挂。
整个站点配置不到 20 行:
{
email admin@pang1220.xyz
}
new.pang1220.xyz {
root * /var/www/new.pang1220.xyz
file_server
encode gzip zstd
header {
Strict-Transport-Security "max-age=31536000; includeSubDomains"
X-Content-Type-Options "nosniff"
X-Frame-Options "SAMEORIGIN"
Referrer-Policy "strict-origin-when-cross-origin"
-Server
}
log {
output file /var/log/caddy/new.pang1220.xyz.log
format json
}
}
几个设计考虑:
:443——这样 Caddy 会自动开启 HTTP→HTTPS 跳转,并自动申请证书。-Server 移除响应头里的 Caddy 版本标识,减少指纹暴露。按计划,应该用 Lighthouse 的域名绑定接口把 new 子域指向新实例。但这一步失败了:
DomainId(驼峰单数),传入后服务端却报 the parameter 'domainIds' is not recognized。而 schema 又禁止传入额外参数(additionalProperties: false),所以 domainIds 也传不进去。两个约束互斥,这条路走不通。
于是这一步回退到在 DNSPod 控制台手动添加一条 A 记录:
| 主机记录 | 类型 | 记录值 | TTL |
|---|---|---|---|
new | A | 实例公网 IP | 600 |
记录添加后需要等待生效。DNSPod 默认 TTL 是 600 秒,实际生效通常在 1~2 分钟内。可以用公共 DNS 验证:
curl -s "https://dns.google/resolve?name=new.pang1220.xyz&type=A"
DNS 一解析到这台机器,Caddy 就会自动做这几件事,全程无需人工干预:
new.pang1220.xyz 的解析已指向本机证书有效期是 90 天,续期完全由 Caddy 内部调度,不需要 cron,也不需要任何人记得这件事。
curl -I https://new.pang1220.xyz 应返回 200;openssl s_client -connect new.pang1220.xyz:443 应能看到一张由 Let's Encrypt 签发、域名匹配的证书。
| 项目 | 结果 |
|---|---|
| 实例创建 | 已完成 |
| 443 端口放通 | 已完成 |
| Caddy 安装运行 | 已完成(v2.11.7,监听 80/443) |
| 页面部署 | 已完成 |
| 安全响应头 | 已配置 |
| DNS 解析绑定 | 接口缺陷,改由控制台手动完成 |
| HTTPS 证书 | DNS 生效后自动签发 |
整条链路里,唯一没有按预期自动完成的是 DNS 绑定。这反而说明了一件事:把自动化建立在单一工具上是有风险的,尤其在接口定义还存在互相矛盾的时候。回退到控制台手动操作虽然慢几十秒,但是确定的。
这台机器是按月付费、手动续费的,一个月后不会自动扣费。它最适合的归宿是在验证完成后直接退掉——一个只放静态页面的 2 核 2 GB 实例,长期挂着没有意义。
如果要把这套流程沉淀成可复用的做法,值得记住的是三条: