---
date: 2026-05-11
tags: [caddy, agent-pool, tls, certificate, infrastructure]
status: documented
---

# Caddy `reload` 不会给新增 hostname 申请证书；agent-pool 写完 Caddyfile 不触发 Caddy 重启

## 现象

刚 `agent-claim` 拿到 slot-1，期望 `https://slot-1.chentao.test.jiachentao.com/` 立即可用，
但浏览器 / curl 都报：

```
curl: (35) tlsv1 alert internal error
```

Caddy 进程在跑、Caddyfile 里**确实**有 slot-1 的 block：
```
slot-1.chentao.test.jiachentao.com {
    handle /api/* { reverse_proxy host.docker.internal:3301 }
    handle { reverse_proxy host.docker.internal:3300 }
}
```

DNS 解析正常（`*.chentao.test.jiachentao.com` 通配指向开发机 public IP）。

## 根因

两层叠加：

1. **`pool-init` / `agent-claim` 写入 Caddyfile 时不重启 Caddy**——只 append slot 块到文件，
   Caddy 依然跑在旧 config 上。
2. **`caddy reload` 对新增 hostname 不可靠**——执行 reload 后日志说 `"config is unchanged"`，
   尽管文件已变（这次实测 reload 没触发 slot-1 的 LE 证书申请）。Caddy 把 reload 当作"hot
   update 现有路由"，新 hostname 的 cert issuance 不进 reload 路径。

结果：Caddy 收到 slot-1.* 的 TLS 请求时，**找不到该 SNI 对应的 cert**，直接发 alert 80
（internal error）拒绝握手。

## 验证

```bash
# 看 Caddy 已颁的证书清单（应该包含目标 hostname）
docker exec caddy ls /data/caddy/certificates/acme-v02.api.letsencrypt.org-directory/
# slot-1.* 不在 → cert 没颁过
```

## 解决

```bash
docker restart caddy
```

启动时 Caddy 重新解析整份 Caddyfile，对每个 hostname（包括新加的）走 ACME 流程。
LE TLS-ALPN-01 challenge 通常 5-15 秒完成。完成后：

```bash
curl -sS -o /dev/null -w "%{http_code}\n" https://slot-1.chentao.test.jiachentao.com/
# → 200
```

**副作用**：重启期间 ~3-5 秒 TLS 中断，所有 worktree 域名的活跃连接断开。其他域名因为已有
stored cert，重启后立即恢复，无需 re-issue。

## 防御性自动化建议

`pool-init` / `agent-claim` 在写完 Caddyfile 后**应该**：
1. 调 `caddy reload`（即便有时候不可靠也比啥都不做强）
2. **更可靠**：在 ACME challenge URL 上 poll 等 cert 出来（最多 30s）
3. 极端 fallback：`docker restart caddy`（中断 3-5s）

当前实现似乎只做了步骤 1 或更少，导致用户每次新开 slot 都得手工等/手工 restart。
可能值得加到 [agent-pool runbook](../scripts/dev/agent-pool/README.md)。

## 进一步学习：默认访问路径

每个 slot **本来就有** Caddy 域名 `slot-{N}.{user}.test.jiachentao.com`，
**不需要** SSH `-L` 隧道。Agent 给用户访问指引时优先给 Caddy 域名，SSH 隧道只是兜底。

`slot-N` 的端口约定（来自 `agent-claim.sh` 的 export）：
- FRONTEND_PORT = 3300/3600/3700/4200/...（按 slot 顺序）
- BACKEND_PORT  = 3301/3601/3701/4201/...（FE+1）

Caddy 块按 `handle /api/*` → BE、`handle` 兜底 → FE 分流，一个域名打通前后端。

## 触发条件 / 何时复现

- 任何新建 slot 之后第一次访问 Caddy 域名
- 任何在 Caddyfile 里手工加新域名之后没 `docker restart caddy`

## 检查清单

下次开 slot 后无法访问 Caddy 域名：
1. `cat /home/chentao/caddy-config/Caddyfile | grep -A4 slot-N` → 配置在不在
2. `docker exec caddy ls /data/caddy/certificates/acme-v02.api.letsencrypt.org-directory/ | grep slot-N` → 证书在不在
3. 不在 → `docker restart caddy`
4. 等 ~10s → `curl -sS -o /dev/null -w "%{http_code}\n" https://slot-N.<user>.test.jiachentao.com/`
