Skip to content

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
	}

要点:

  1. 用容器名 + 容器端口(Caddy 在 tenodx-net 里),不要写 127.0.0.1
  2. 上游必须是根路径可访问的服务。若它只支持子路径,就得用独立 Host 或独立端口
  3. WebSocket 不用特殊配置,reverse_proxy 自动处理
  4. 改完 docker compose restart caddy
  5. 按 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 字段过滤即可只看访问记录。

TenoDX · 内部文档