主题
12 — Web 入口(Caddy 反向代理)
部署时间:2026-11-08 05:18~05:25 CST Caddy v2.11.9,容器化,仅监听
127.0.0.1:80,通过 SSH 隧道访问。
1. 为什么装 Caddy / 为什么这样配
装它的理由
服务器已有 3 个服务(PostgreSQL / Uptime Kuma / NapCat),都是容器 + loopback 端口。 Caddy 提供一个统一的 Web 入口:集中鉴权、压缩、访问日志,以及未来上真域名时的 HTTPS 终结点。
⚠️ 关键约束:两个应用都不支持子路径
部署前实测确认:
| 应用 | 结论 | 依据 |
|---|---|---|
| Uptime Kuma 2.5.9 | 不支持 base path | 镜像里没有任何 BASE_URL / basePath 相关环境变量 |
| NapCat WebUI | 不能用子路径 | 静态资源全是绝对路径:/webui/assets/index-*.js |
所以
/uptime/、/napcat/这类路径式反代会直接坏掉(资源 404、绝对跳转跑飞)。 只能用 Host 头做虚拟主机 —— 这是本配置的根本原因。
为什么不开自动 HTTPS
自动证书需要:①公网可达 80/443 ②真实域名解析到本机。 当前安全组只放行 22,两个条件都不满足,所以 auto_https off。 配置里已留好将来切换的完整注释块。
2. 部署信息
| 项 | 值 |
|---|---|
| 镜像 | caddy:2.11.9-alpine(锁版本,不用 latest) |
| 部署目录 | /opt/stacks/caddy/ |
| 容器名 | tenodx-caddy |
| 监听 | 127.0.0.1:80(容器内声明了 443 但未映射) |
| 内存 | ~11 MB |
| 网络 | tenodx-net |
| 重启策略 | unless-stopped |
目录结构
/opt/stacks/caddy/
├── compose.yaml # caddy:2.11.9-alpine
├── Caddyfile # 路由 + basic auth(含 bcrypt 哈希,640)
├── site/index.html # 落地页(静态)
├── logs/access.log # 访问日志,20MB × 5 轮转
└── .env # basic auth 用户名 + 明文密码(600)3. 路由规则
caddyfile
http://:80 {
encode zstd gzip
handle /healthz { respond "ok" 200 } # 不鉴权,供监控探测
@kuma host kuma.localhost # Uptime Kuma
handle @kuma { basic_auth { ... } reverse_proxy tenodx-uptime-kuma:3001 }
@napcat host napcat.localhost # NapCat WebUI
handle @napcat { basic_auth { ... } reverse_proxy tenodx-napcat:6099 }
handle { root * /srv; try_files {path} /index.html; file_server } # 落地页
}路由顺序很关键:/healthz 放在最前且不鉴权,否则监控会拿到 401 而误判宕机。
4. 访问方式
推荐:一个隧道搞定所有 Web 面板
powershell
powershell -NoProfile -ExecutionPolicy Bypass -File .\server-info\scripts\tunnel.ps1 -Service web| 地址 | 说明 |
|---|---|
http://127.0.0.1:13080/ | 落地页(不需要鉴权) |
http://kuma.localhost:13080/ | Uptime Kuma |
http://napcat.localhost:13080/webui/ | NapCat WebUI |
http://127.0.0.1:13080/healthz | 健康探针,返回 ok |
⚠️ 前提:*.localhost 要能解析
Chrome / Edge / Firefox 按规范会把 任意.localhost 解析到 127.0.0.1,通常开箱可用。 若你的浏览器不行,往 hosts 里加一行:
# C:\Windows\System32\drivers\etc\hosts
127.0.0.1 kuma.localhost napcat.localhost备选:绕过 Caddy 直连(永远可用)
powershell
... -Service uptime # http://127.0.0.1:13001 (无 basic auth)
... -Service napcat # http://127.0.0.1:16099/webui/直连没有 basic auth,但需要先过隧道(SSH 本身已鉴权),安全性可接受。 两条路任选,互不影响。
5. Basic Auth
| 项 | 值 |
|---|---|
| 用户名 | tenodx |
| 密码 | /opt/stacks/caddy/.env 的 CADDY_BASIC_PASSWORD |
| 哈希 | 内联在 Caddyfile 里(bcrypt,640 权限) |
| 覆盖范围 | kuma.localhost 和 napcat.localhost;落地页与 /healthz 不鉴权 |
⚠️ 踩坑(重要):bcrypt 哈希不能走 compose 环境变量
第一版把哈希放在 .env + compose.yaml 的 environment: 里,结果带正确密码也返回 401。
原因:bcrypt 哈希长这样 $2a$14$AbC...,Docker Compose 会对环境变量的值做 $ 插值, 把每个 $xxx 当成变量名替换成空串,哈希到达 Caddy 时已经被搅烂。 日志里的证据:
The "j6giqe3" variable is not set. Defaulting to a blank string.解决:哈希内联进 Caddyfile(Caddy 只认 {$VAR} 语法,裸 $2a$14$ 是字面量)。
改密码
bash
# 1) 生成新哈希
docker run --rm caddy:2.11.9-alpine caddy hash-password --plaintext '你的新密码'
# 2) 替换 Caddyfile 里 basic_auth 块中的哈希(两处:kuma + napcat)
vi /opt/stacks/caddy/Caddyfile
# 3) 校验并重载
docker exec tenodx-caddy caddy validate --config /etc/caddy/Caddyfile
cd /opt/stacks/caddy && docker compose restart caddy关闭 basic auth
把 Caddyfile 里两个 basic_auth { ... } 块删掉,然后重启容器即可。
6. 新增一个站点(以后照此扩展)
caddyfile
# --- 新服务 ---
@myapp host myapp.localhost
handle @myapp {
basic_auth {
tenodx <同一个哈希>
}
reverse_proxy tenodx-myapp:8080
}要点:
- 用容器名 + 容器端口(Caddy 在
tenodx-net里),不要写127.0.0.1 - 上游必须是根路径可访问的服务。若它只支持子路径,就得用独立 Host 或独立端口
- WebSocket 不用特殊配置,
reverse_proxy自动处理 - 改完
docker compose restart caddy - 按
10-部署规范.md第 8 条,同时在monitors.json加监控
7. 将来上真域名 + HTTPS
前置条件:域名解析到本机 + 安全组放行 80/443。
caddyfile
# 1) 全局块改成:auto_https on
# 2) 把 http://:80 { ... } 整块替换为:
your.domain.example {
encode zstd gzip
handle /healthz { respond "ok" 200 }
@kuma host kuma.your.domain.example
handle @kuma { basic_auth { ... } reverse_proxy tenodx-uptime-kuma:3001 }
handle { root * /srv; file_server }
}yaml
# compose.yaml 端口改成公网映射
ports:
- "80:80"
- "443:443"Caddy 会自动申请并续期证书,无需额外配置。
8. 验证结果(2026-11-08 05:22)
| 检查项 | 结果 |
|---|---|
| Caddyfile 校验 | ✅ Valid configuration |
/healthz | ✅ 200 ok |
落地页 / | ✅ 200(3537 bytes) |
kuma.localhost 无凭据 | ✅ 401(鉴权生效) |
kuma.localhost 有凭据 / | ✅ 302 → /dashboard(Uptime Kuma 自身行为) |
kuma.localhost 有凭据 /dashboard | ✅ 200,<title>Uptime Kuma |
napcat.localhost 无凭据 | ✅ 401 |
napcat.localhost 有凭据 /webui/ | ✅ 200,<title>NapCat WebUI |
| 经 SSH 隧道访问 | ✅ 落地页 200、/healthz 200、Host 路由 401 |
| 静态资源穿透 | ✅ /favicon.ico 200 |
| Uptime Kuma 已纳入监控 | ✅ Caddy 项 UP 200 - OK |
| 公网监听 | ✅ 仍只有 22/tcp |
9. 运维命令
bash
cd /opt/stacks/caddy
docker compose ps # 状态
docker compose logs --tail 50 # Caddy 运行日志
docker compose restart # 重载配置(改 Caddyfile 后)
docker exec tenodx-caddy caddy validate --config /etc/caddy/Caddyfile
docker exec tenodx-caddy caddy fmt --overwrite /etc/caddy/Caddyfile
tail -f /opt/stacks/caddy/logs/access.log | jq -r '[.ts,.request.remote_ip,.request.host,.request.uri,.status]|@tsv'
logs/access.log里同时有 Caddy 自身的运行日志和 HTTP 访问记录(全局log块 接管了默认 logger)。按.request字段过滤即可只看访问记录。